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

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