JVM運行時區(qū)域

JVM運行時數(shù)據(jù)區(qū),主要包含兩大部分:線程共有的方法區(qū)(Method Area)和堆(Heap),線程私有的虛擬機(jī)棧(VM Stack)、本地方法棧(Native Method Stack)和程序計數(shù)器(Program Counter Register)。在數(shù)據(jù)區(qū)下面的執(zhí)行引擎中又包含了:即時編譯器(JITCompiler)和垃圾收集器(GC)。GC主要用于回收線程共享的區(qū)域(方法區(qū)和堆),對于線程私有的內(nèi)存區(qū)域則方法執(zhí)行完畢后系統(tǒng)自動釋放。(在實際的程序中,線程私有的內(nèi)存區(qū)域會有很多份)。
對于整個運行時數(shù)據(jù)區(qū)而言,外部交互的模塊有執(zhí)行引擎、本地庫接口和類加載器。
-
程序計數(shù)器(線程私有)
是一塊較小的內(nèi)存空間,它的作用可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機(jī)概念模型里,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址,如果正在執(zhí)行的是Native方法,這個計數(shù)器值則為空(Undefined)。此內(nèi)存區(qū)域是唯一一個在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
-
JVM棧(線程私有)
生命周期同線程相同。描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法執(zhí)行時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。每個方法被調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、引用類型和returnAddress類型(指向了一條字節(jié)碼指令的地址)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
JVM規(guī)范對這個區(qū)域規(guī)定了兩種異常狀況:如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展(當(dāng)前大部分Java虛擬機(jī)可以動態(tài)擴(kuò)展),當(dāng)擴(kuò)展時無法申請到足夠的內(nèi)存時會拋出OutOfMemoryError異常。
-
本地方法棧(線程私有)
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。(HotSpot虛擬機(jī)直接把本地方法棧和虛擬機(jī)棧合二為一)。
-
Java堆(線程共享)
Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。Java虛擬機(jī)規(guī)范描述是,所有的對象實例以及數(shù)組都要在堆上分配。但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了。
Java堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱為“GC堆”。
Java堆可以處于物理上不連續(xù)的內(nèi)存空間,只要邏輯上是連續(xù)的即可。主流的虛擬機(jī)都是按照可擴(kuò)展來實現(xiàn)的(通過-Xmx和-Xms控制)。如果在堆中沒有內(nèi)存來完成實例分配,并且堆也無法再擴(kuò)展時,將會拋出OutOfMemoryError異常。
-
方法區(qū)(線程共享)
用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
Java虛擬機(jī)規(guī)范對這個區(qū)域的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不是先垃圾收集。相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的。這個區(qū)域的垃圾回收目標(biāo)主要是針對常量池的回收和對類型的卸載。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。
-
運行時常量池
運行時常量池是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期間生成的各種字面量和符號引用。這部分內(nèi)容將在類加載后存放到方法區(qū)的運行時常量池中。
Java虛擬機(jī)對Class文件的每一部分(包括常量池)的格式都有嚴(yán)格的規(guī)定,每一個字節(jié)用于存儲哪種數(shù)據(jù)都必須符合規(guī)范上的要求,這樣才會被虛擬機(jī)認(rèn)可、裝載和執(zhí)行。但對于運行時常量池,Java虛擬機(jī)規(guī)范沒有做任何細(xì)節(jié)的要求,不同的提供商實現(xiàn)的虛擬機(jī)可以按照自己的需求來實現(xiàn)這個內(nèi)存區(qū)域。
運行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性。運行期間也可以將新的常量放入池中,比如String類的intern()方法。
當(dāng)常量池?zé)o法再申請到內(nèi)存時,會拋出OutOfMemoryError異常。