Java虛擬機(jī)-內(nèi)存區(qū)域
Java虛擬機(jī)在執(zhí)行程序時(shí)會(huì)把它所管理的內(nèi)存劃分為若干個(gè)區(qū)域。分別為線程共享的方法區(qū)、堆和線程隔離的程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧。

程序計(jì)數(shù)器
程序計(jì)數(shù)器可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。每個(gè)線程都擁有自己的程序計(jì)數(shù)器(指示該線程執(zhí)行到的位置),這部分內(nèi)存為線程“私有”的內(nèi)存。
如果線程正在執(zhí)行java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的java指令地址。如果正在執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器的值為空(Undefined)。
這個(gè)內(nèi)存區(qū)域是唯一一個(gè)在java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。
Java虛擬機(jī)棧
Java虛擬機(jī)棧的生命周期與線程相同
虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型。每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每個(gè)方法的執(zhí)行,就對(duì)應(yīng)一個(gè)棧幀的入棧到出棧。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean,byte,char,short,int,float,long,double)、對(duì)象引用(reference類型)和returnAddress類型(指向了一條字節(jié)碼指令的類型)。這些數(shù)據(jù)類型在局部變量表中的存儲(chǔ)空間以局部變量槽(Slot)來(lái)表示,其中64位長(zhǎng)度的long和double的數(shù)據(jù)會(huì)占用兩個(gè)局變量槽,其他類型只占用一個(gè)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,方法運(yùn)行期間不會(huì)改變局部變量表的大?。ㄗ兞坎鄣臄?shù)量)。
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩個(gè)異常狀態(tài)。如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常。如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展,擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常(HotSpot虛擬機(jī)的棧容量是不可以動(dòng)態(tài)擴(kuò)展的,只要線程申請(qǐng)??臻g成功了就不會(huì)有OutOfMemoryError,但如果申請(qǐng)失敗還是會(huì)拋出OutOfMemoryError)。
本地方法棧
本地方法棧和虛擬機(jī)棧所發(fā)揮的作用是相似的,他們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。
本地方法棧也會(huì)拋出StackOverflowError和OutOfMemoryError異常。
Java堆
Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
Java堆的唯一目的就是存放對(duì)象實(shí)例,幾乎所有對(duì)象實(shí)例都在這里分配。(所有的對(duì)象實(shí)例一季數(shù)組都要在堆上分配)但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生,所有對(duì)象都分配在堆上也漸漸變得不是那么“絕對(duì)”。
Java堆是垃圾收集器管理的主要區(qū)域,因此也被稱作“GC堆”(Garbage Collected Heap)。由于現(xiàn)在收集器都采用分代收集算法,所以Java堆還可以細(xì)分為新生代、老年代、Eden空間、From Survivor空間、To Survivor空間等。從內(nèi)存分配的角度看,線程共享的Java堆中可能劃分出多個(gè)線程私有的本地線程分配緩沖區(qū)(Thread Local Allocation Buffer, TLAB)。
根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,Java堆可以處于物理上不連續(xù)的空間中,大小通過(guò)-Xms和-Xmx控制。如果Java堆中沒(méi)有內(nèi)存完成實(shí)例分配,堆也無(wú)法再擴(kuò)展時(shí),Java虛擬機(jī)將會(huì)拋出OutOfMemoryError異常。
方法區(qū)(Method Area、Non-Heap)
方法區(qū)用于存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯后的代碼緩存等數(shù)據(jù)。
在HotSpot虛擬機(jī)上,尤其是在JDK8以前,方法區(qū)又被稱為“永久代”,僅僅是因?yàn)镠otSpot虛擬機(jī)將GC分代收集擴(kuò)展至方法區(qū)。但這樣設(shè)計(jì)更容易遇到內(nèi)存溢出問(wèn)題,永久代有-XX:MaxPermSize的上限,而J9和JRocket只要不觸碰到進(jìn)程可用內(nèi)存上限就不會(huì)出現(xiàn)問(wèn)題。(JDK1.7的HotSpot中,已經(jīng)將字符串常量池移出“永久代”, 到了JDK8, 終于完全廢棄了永久代的概念,改用與JRocket、J9一樣在本地內(nèi)存中實(shí)現(xiàn)的元空間來(lái)代替)。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配的需求時(shí),將拋出OutOfMemoryError異常.
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中,出了類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池表,用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
直接內(nèi)存
直接內(nèi)存(Direct Memory)不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)域的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。
直接內(nèi)存使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后使用存儲(chǔ)在堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。
忽略直接內(nèi)存也可能導(dǎo)致OutOfMemoryError異常。(各大內(nèi)存區(qū)域加上直接內(nèi)存超過(guò)物理內(nèi)存,導(dǎo)致其他區(qū)域無(wú)法擴(kuò)展)。