武漢模板自助建站seo技術優(yōu)化服務
目錄
什么是運行時數(shù)據(jù)區(qū)?
方法區(qū)
堆
程序計數(shù)器
虛擬機棧
局部變量表? ??
操作數(shù)棧? ? ? ?
動態(tài)連接
運行時常量池
方法返回地址
附加信息
本地方法棧
總結(jié):
什么是運行時數(shù)據(jù)區(qū)?
? ? ?Java虛擬機在執(zhí)行Java程序時,將它管理的內(nèi)存分為不同的區(qū)域。這些區(qū)域用途不同,創(chuàng)建和銷毀的時間也不同。有的隨虛擬機進程啟動一直存在,有的依賴用戶線程啟動和結(jié)束而創(chuàng)建和銷毀。根據(jù)《Java虛擬機規(guī)范》,Java虛擬機管理的內(nèi)存區(qū)域包括以下幾個運行時數(shù)據(jù)區(qū)域。

方法區(qū)
????????方法區(qū)用于存儲已被虛擬機加載的類型信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼緩存等數(shù)據(jù)。可以理解為被線程共享的內(nèi)存區(qū)域。
? ???????根據(jù)《Java虛擬機規(guī)范》的規(guī)定,如果方法區(qū)無法滿足新的內(nèi)存分配需求時,將拋出
OutOfMemoryError異常。
堆
? ? ? ? Java堆(Java heap)是虛擬機管理的最大一塊內(nèi)存,線程共享,虛擬機啟動時創(chuàng)建,用于存儲對象實例。雖然在《Java虛擬機規(guī)范》中對Java堆的描述是:“所有 的對象實例以及數(shù)組都應當在堆上分配”,但隨著即時編譯技術的發(fā)展,棧上分配,標量替換等優(yōu)化手段,Java實例不僅僅分配在堆上。
? ? ? ? Java堆是垃圾回收的主要區(qū)域,HotSpot VM 的堆內(nèi)存又分為新生代、老年代和永久代。新生代又分為Eden空間和Survivor空間。 常見的垃圾收集器也都是圍繞這些內(nèi)存區(qū)域進行工作的。將Java堆細分,主要是為了更好的回收內(nèi)存或更快分配內(nèi)存。
????????根據(jù)《Java虛擬機規(guī)范》的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,但在邏輯上應是連續(xù)的,就像我們使用磁盤空間存儲文件一樣,并不要求所有文件連續(xù)存放。但對于大對象(典型的如數(shù)組對象),多數(shù)虛擬機實現(xiàn)出于實現(xiàn)簡單、存儲高效的考慮,很可能會要求連續(xù)的 內(nèi)存空間。
????????Java堆既可以被實現(xiàn)成固定大小的,也可以是可擴展的,不過當前主流的Java虛擬機都是按照可擴展來實現(xiàn)的(通過參數(shù)-Xmx和-Xms設定)。如果在Java堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,Java虛擬機將會拋出OutOfMemoryError異常。
程序計數(shù)器
? ? ? 因為Java虛擬機中的多線程通過線程輪流切換、分配處理器的執(zhí)行時間的方式實現(xiàn),在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令。為了在線程切換后準確恢復到正確的執(zhí)行位置,需要記錄每個線程的所執(zhí)行的字節(jié)碼的行號,這時就需要程序計數(shù)器。
? ? ? 程序計數(shù)器是一塊較小的內(nèi)存,每個線程都需要一個獨立的程序計數(shù)器,每個線程之間程序計數(shù)器互不影響,獨立存儲,是“線程私有”的內(nèi)存。在Java虛擬機的概念模型里,通過改變程序計數(shù)器的值選取下一條需要執(zhí)行的字節(jié)碼指令。程序計數(shù)器,是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復等基礎都需要依賴這個計數(shù)器完成。大家會想到程序在計算機上執(zhí)行過程中的程序技術器,決定著程序執(zhí)行的流程。
? ? ? ?如果執(zhí)行的是本地(Native)方法,程序計數(shù)器的值則應為空(undefined),此區(qū)域是為一個一個在《Java虛擬機規(guī)范》中沒有任何OutOfMemeoryError情況的區(qū)域。
虛擬機棧
? ? ? ? Java虛擬機棧,也是線程私有的,和線程的生命周期相同。
? ? ? ? 每個方法被執(zhí)行時,Java虛擬機會同步創(chuàng)建一個棧幀,每個方法被調(diào)用直至執(zhí)行完畢的過程,就對應著一個棧幀在虛擬機棧中剛從入棧到出棧的過程。
棧幀由幾部分組成?
? ? ? 局部變量表,操作數(shù)棧,方法返回地址,動態(tài)連接,附加信息等。
局部變量表? ??
? ? ? ? 我們知道方法由:訪問修飾符(可選),返回值,方法名,參數(shù)列表,方法體組成。
????????局部變量表,保存方法參數(shù)和方法內(nèi)部的局部變量,在局部變量表中的存儲空間以變量槽(slot)為單位,每個槽都應該能存放一個boolean,byte,char,short,int,float,reference或returnAddress類型的數(shù)據(jù),這8種數(shù)據(jù)類型都可以使用32位或更小的物理內(nèi)存累存儲,且隨著處理器、操作系統(tǒng)或虛擬機實現(xiàn)的不同而發(fā)生變化。局部變量表的空間在編譯期即可根據(jù)源碼和虛擬機的具體棧內(nèi)存實現(xiàn)方式確定,不會受程序運行時數(shù)據(jù)的影響。
操作數(shù)棧? ? ? ?
????????操作數(shù)棧,是一個先進后出的棧結(jié)構(gòu),用于臨時保存方法執(zhí)行過程的操作數(shù)。比如在進行加法運算:1+2=3時。
1. 將第一個操作數(shù) 1 壓入操作數(shù)棧;
2. 將第二個操作數(shù) 2 壓入操作數(shù)棧;
3. 從操作數(shù)棧彈出第二個操作數(shù) 2;
4. 從操作數(shù)棧彈出第一個操作數(shù) 1;
5. 將兩個操作數(shù)相加得到結(jié)果 3;
6. 將結(jié)果 3 壓入操作數(shù)棧;
7. 從操作數(shù)棧彈出結(jié)果 3;
? ? ? ?通過操作數(shù)棧,虛擬機可以方便地對運算中的操作數(shù)進行入棧和出棧,實現(xiàn)復雜的算術運算邏輯。它避免了每次運算都需要在堆內(nèi)存中分配新的操作數(shù)對象,可以提高執(zhí)行效率。高效地對方法運算過程中的操作數(shù)進行入棧出棧操作,這是實現(xiàn)Java虛擬機高效運行的重要組成部分。每個棧幀的操作數(shù)棧的深度,在編譯器也可確定,在運行期間不會變。
動態(tài)連接
? ? ? ? 在講動態(tài)連接之前,我們先回顧一下運行時常量池的知識。
運行時常量池
? ? ? ? 運行時常量池位于方法區(qū)。Class文件中除了有類的版本、字段、方法、接口等描述信息,還有一項是常量池表,用于存放編譯期生成的各種字面量和符號引用,常量池表中這部分內(nèi)容將在類加載后存放在方法區(qū)的常量池中,運行期間也可以將新的常量放到常量池中。
? ? ? ? Class文件的常量池中存在大量的符號引用,字節(jié)碼中的方法調(diào)用指令就以常量池里指向方法的符號引用作為參數(shù)。這些符號引用一部分會在類加載階段或者第一次使用的時候被轉(zhuǎn)為直接引用,這種轉(zhuǎn)化被稱為靜態(tài)解析。另一部分將在每次運行期間都轉(zhuǎn)化為直接引用,這部分稱為動態(tài)連接。
? ? ? ? 動態(tài)連接是指在程序運行時才去解析字節(jié)碼中的符號引用,并把符號引用替換為直接引用的過程,主要為了支持Java動態(tài)綁定機制。動態(tài)綁定允許程序運行時才去決定實際調(diào)用的方法,給Java帶來很大的靈活性,支持Java的多態(tài)特性。
舉個例子:
public class Main {public static void main(String[] args) {Animal a = new Dog(); a.run();}
}class Animal {public void run() {System.out.println("Animal is running");}
}class Dog extends Animal {@Overridepublic void run() {System.out.println("Dog is running"); }
}
????????編譯時,編譯器只知道a是一個Animal對象,它不知道a最終會指向一個Dog對象。
????????運行時,JVM通過動態(tài)連接,才會根據(jù)實際的對象類型Dog,動態(tài)地綁定到Dog.run()這個方法上,從而輸出"Dog is running"。如果沒有動態(tài)連接,那么只能靜態(tài)地綁定到Animal.run(),就無法利用多態(tài)的特性了。所以動態(tài)連接是支持運行時多態(tài)以及動態(tài)綁定的關鍵。它讓Java語言可以更靈活地處理對象的多態(tài)特性。? ? ? ?
方法返回地址
? ? ? ? 方法執(zhí)行完退出時,無論是遇到方法返回的字節(jié)碼指令還是遇到異常退出,都需要返回到最初方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行。這個位置就是方法返回地址。
例如:
public int sum(int a, int b) {return a + b;
}public static void main(String[] args) {int s = sum(1, 2);System.out.println(s);
}
????????在main方法調(diào)用sum方法時,會先記錄下?“int s = sum(1,2);”?這行代碼的位置,當sum方法執(zhí)行完返回結(jié)果后,返回到該位置,執(zhí)行后面的“System.out.pringln(s);”方法。所以,方法返回地址,就是調(diào)用該方法的具體代碼位置。
????????JVM通過動態(tài)連接找到方法的入口,并記錄下返回地址,以便在方法執(zhí)行后正確返回到調(diào)用方。返回地址是實現(xiàn)正確的方法調(diào)用流程所必需的。動態(tài)連接使得返回地址可以在運行時確定,這樣Java程序才可以實現(xiàn)動態(tài)綁定和靈活的函數(shù)調(diào)用機制。?
? ? ? ? 而方法返回后,該方法對應的棧幀會出棧,棧頂?shù)臈褪窃摲椒ǖ恼{(diào)用者,調(diào)用者的局部變量表可能會發(fā)生變化,這取決于方法的返回值是否被賦值給了調(diào)用者棧幀的某個局部變量。還以上面的方法為例,當sum方法執(zhí)行完返回后,main方法棧幀的局部變量s被賦值為sum方法的返回值3。
附加信息
????????附加信息,是指在進行方法調(diào)用時,除了明確的參數(shù)和返回值之外,還可以傳遞的一些額外信息。從理論上講,它提供了一種傳遞方法調(diào)用的額外上下文的方式,對JVM內(nèi)部來說可以提供更多信息。一些專業(yè)的程序分析和追蹤工具可能會用到它們,對日常開發(fā)影響不大。
本地方法棧
????????本地方法棧(Native Method Stack):與虛擬機棧類似,用于支持Native方法的執(zhí)行。關于本地方法,可參考Java本地方法/Java native方法/JNI_jni native方法_小王師傅66的博客-CSDN博客
總結(jié):
? ? ? ? JVM運行時數(shù)據(jù)區(qū)主要包括:方法區(qū),堆,虛擬機棧,程序計數(shù)器,本地方法棧。
????????方法區(qū)(Method Area):用于存儲類信息、靜態(tài)變量、靜態(tài)方法等數(shù)據(jù),可以理解為所有線程共享的內(nèi)存區(qū)域。方法區(qū)無法滿足新的內(nèi)存分配需求時,會拋出OutOfMemeoryError異常;
????????堆內(nèi)存(Heap):用于存儲對象實例,可以理解為所有線程共享的內(nèi)存區(qū)域。如果在Java堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,Java虛擬機將會拋出OutOfMemoryError異常??梢酝ㄟ^調(diào)整-Xms和-Xmx參數(shù)調(diào)整堆空間;
????????虛擬機棧(VM stack):用于存儲局部變量表、操作棧、動態(tài)鏈接、方法返回地址/方法出口等信息,屬于線程私有。每個線程都有自己的虛擬機棧。當線程請求的棧深度超過虛擬機所允許的最大深度時,就會拋出 StackOverflowError 異常。當虛擬機棧的空間無法分配時,將拋出 OutOfMemoryError 異常。可以通過調(diào)整-Xss參數(shù)調(diào)整??臻g;
????????程序計數(shù)器(PC Register):用于存儲指向下一條將要執(zhí)行的指令的地址,每個線程都有自己的程序計數(shù)器,它的空間是非常小的,基本不會發(fā)生溢出的情況。
????????本地方法棧(Native Method Stack):與虛擬機棧類似,用于支持Native方法的執(zhí)行。本地方法棧也是線程私有,與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError和OutOfMemoryError異常。