JVM內(nèi)存模型如何分配的?

01 JVM內(nèi)存模型的劃分

由于我們生產(chǎn)環(huán)境使用的虛擬機HotSpot 居多,所以下面的描述都是基于HotSpot 虛擬機而言的,對于其他類型的虛擬機,如 JRockit(Oracle)、J9(IBM) 可能并不太一樣

根據(jù)虛擬機規(guī)范,JVM的內(nèi)存分為 堆、虛擬機棧、本地方法棧、程序計數(shù)器、本地方法棧5部分

JDK 1.8 同 JDK 1.7 比,最大的差別就是:元數(shù)據(jù)區(qū)取代了永久代。元空間的本質(zhì)和永久代類似,都是對 JVM 規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元數(shù)據(jù)空間并不在虛擬機中,而是使用本地內(nèi)存

1.1 虛擬機棧

每個線程有一個私有的棧,隨著線程的創(chuàng)建而創(chuàng)建。棧里面存著的是一種叫“棧幀”的東西,每個方法會創(chuàng)建一個棧幀,棧幀中存放了局部變量表(基本數(shù)據(jù)類型和對象引用)、操作數(shù)棧、方法出口等信息。棧的大小可以固定也可以動態(tài)擴展。當(dāng)棧調(diào)用深度大于JVM所允許的范圍,會拋出StackOverflowError的錯誤

虛擬機棧的特點

  • 局部變量表隨著棧幀的創(chuàng)建而創(chuàng)建,它的大小在編譯時確定,創(chuàng)建時只需分配事先規(guī)定的大小即可。在方法運行過程中,局部變量表的大小不會發(fā)生改變
  • Java 虛擬機棧也是線程私有,隨著線程創(chuàng)建而創(chuàng)建,隨著線程的結(jié)束而銷毀
  • Java 虛擬機棧會出現(xiàn)兩種異常:StackOverFlowError (若 Java 虛擬機棧的大小不允許動態(tài)擴展,那么當(dāng)線程請求棧的深度超過當(dāng)前 Java 虛擬機棧的最大深度時,拋出 StackOverFlowError 異常)和 OutOfMemoryError(若允許動態(tài)擴展,那么當(dāng)線程請求棧時內(nèi)存用完了,無法再動態(tài)擴展時,拋出 OutOfMemoryError 異常)

1.2 本地方法棧

本地方法棧是為 JVM 運行 Native 方法準備的空間,由于很多 Native 方法都是用 C 語言實現(xiàn)的,所以它通常又叫 C 棧。它與 Java 虛擬機棧實現(xiàn)的功能類似,只不過本地方法棧是描述本地方法運行過程的內(nèi)存模型。本地方法被執(zhí)行時,在本地方法棧也會創(chuàng)建一塊棧幀,用于存放該方法的局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口信息等。方法執(zhí)行結(jié)束后,相應(yīng)的棧幀也會出棧,并釋放內(nèi)存空間。也會拋出 StackOverFlowError 和 OutOfMemoryError 異常。

1.3 PC 寄存器計數(shù)器

PC 寄存器,也叫程序計數(shù)器。JVM支持多個線程同時運行,每個線程都有自己的程序計數(shù)器。倘若當(dāng)前執(zhí)行的是 JVM 的方法,則該寄存器中保存當(dāng)前執(zhí)行指令的地址;倘若執(zhí)行的是native 方法,則PC寄存器中為空

1.4 堆

堆內(nèi)存是 JVM 所有線程共享的部分,在虛擬機啟動的時候就已經(jīng)創(chuàng)建。所有的對象和數(shù)組都在堆上進行分配。這部分空間可通過 GC 進行回收。當(dāng)申請不到空間時會拋出 OutOfMemoryError

堆的特點:

  • 線程共享,整個 Java 虛擬機只有一個堆,所有的線程都訪問同一個堆。而程序計數(shù)器、Java 虛擬機棧、本地方法棧都是一個線程對應(yīng)一個
  • 在虛擬機啟動時創(chuàng)建
  • 垃圾回收的主要場所
  • 可分為:新生代(Eden區(qū) From Survior To Survivor)、老年代

1.5 方法區(qū)

  • Java 虛擬機規(guī)范中定義方法區(qū)是堆的一個邏輯部分。
  • 方法區(qū)也是所有線程共享。主要用于存儲已經(jīng)被虛擬機加載的類的信息、常量池、方法數(shù)據(jù)、方法代碼等。方法區(qū)邏輯上屬于堆的一部分,但是為了與堆進行區(qū)分,通常又叫 非堆 。需要注意的是,方法區(qū)只是規(guī)范上面的一個邏輯概念,并不是真實的物理存儲的命名

1.6 直接內(nèi)存(堆外內(nèi)存)

直接內(nèi)存是除 Java 虛擬機之外的內(nèi)存,但也可能被 Java 使用,直接內(nèi)存的大小不受 Java 虛擬機控制,但既然是內(nèi)存,當(dāng)內(nèi)存不足時就會拋出 OutOfMemoryError 異常

直接內(nèi)存與堆內(nèi)存比較:

  • 直接內(nèi)存申請空間耗費更高的性能
  • 直接內(nèi)存讀取 IO 的性能要優(yōu)于普通的堆內(nèi)存

02 JVM內(nèi)存模型各部分的存儲信息

03 針對JDK6、JDK7、JDK8三個版本的JVM內(nèi)存模型調(diào)整說明

3.1 對永久代PermGen的說明

  • 永久代是方法區(qū)在hotspot的一個具體實現(xiàn)。通過在運行時數(shù)據(jù)區(qū)域開辟空間實現(xiàn)方法區(qū)。
  • hotspot jdk7 之前的永久代,比較完整
  • 從jdk7以后方法區(qū)就“四分五裂了”,不再是在單一的一個去區(qū)域內(nèi)進行存儲
  • 在jdk8,移除了永久代,被Metsspace取代了,且Metsspace不在JVM堆內(nèi),放入了本地內(nèi)存,元空間也就成了方法區(qū)的主要存放位置

絕大部分 Java 程序員應(yīng)該都見過 java.lang.OutOfMemoryError: PermGen space這個異常

這里的 “PermGen space”其實指的就是方法區(qū)。不過方法區(qū)和“PermGen space”又有著本質(zhì)的區(qū)別。前者是 JVM 的規(guī)范,而后者則是 JVM 規(guī)范的一種實現(xiàn),并且只有 HotSpot 才有 “PermGen space”,而對于其他類型的虛擬機,如 JRockit(Oracle)、J9(IBM) 并沒有“PermGen space”。由于方法區(qū)主要存儲類的相關(guān)信息,所以對于動態(tài)生成類的情況比較容易出現(xiàn)永久代的內(nèi)存溢出。最典型的場景就是,在 jsp 頁面比較多的情況,容易出現(xiàn)永久代內(nèi)存溢出

3.2 對Metaspace元空間的說明

元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。元空間通過在本地內(nèi)存區(qū)域開辟空間實現(xiàn)方法區(qū)。元空間并不在虛擬機中,而是使用本地內(nèi)存,所以默認情況下,元空間的大小僅受本地內(nèi)存限制,但可以通過以下參數(shù)來指定元空間的大?。?/p>

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發(fā)垃圾收集進行類型卸載,同時GC會對該值進行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當(dāng)提高該值。-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。除了上面兩個指定大小的選項以外,還有兩個與 GC 相關(guān)的屬性:-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集

其實,移除永久代的工作從JDK1.7就開始了,JDK1.7中,存儲在永久代的部分數(shù)據(jù)就已經(jīng)轉(zhuǎn)移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并沒完全移除。

譬如:

  • 符號引用(Symbols)轉(zhuǎn)移到了native heap;
  • 字面量(interned strings)轉(zhuǎn)移到了java heap;
  • 類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap

3.3 元空間特點

  • 類及相關(guān)的元數(shù)據(jù)的生命周期與類加載器的一致,如果GC發(fā)現(xiàn)某個類加載器不再存活了,才會把相關(guān)的空間整個回收掉
  • 每個類加載器有專門的存儲空間
  • 只進行線性分配,省掉了GC掃描及壓縮的時間
  • 元空間里的對象的位置是固定的

04 JVM內(nèi)存劃分調(diào)整的幾個原因點分析

  • 字符串存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出
  • 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出
  • 永久代會為 GC 帶來不必要的復(fù)雜度,并且回收效率偏低
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容