JVM內存堆布局圖解分析

jopen 9年前發布 | 16K 次閱讀 JVM Java開發

JAVA能夠實現跨平臺的一個根本原因,是定義了class文件的格式標準,凡是實現該標準的JVM都能夠加載并解釋該class文件,據此也可以知道,為啥Java語言的執行速度比C/C++語言執行的速度要慢了,當然原因肯定不止這一個,如在JVM中沒有數據寄存器,指令集使用的是棧來保存中間數據...等,盡管Java的貢獻者們為執行速度的提高想了各種辦法,如JIT、動態編譯器等,以下是Leetcode中一道題目用不同的語言實現時的執行性能對比圖...

 JVM內存堆布局圖解分析

以下是JVM的一個基本架構圖,在這個基本架構圖中,棧有兩部份,Java線程棧以及本地方法棧,棧的概念與C/C++程序基本上都是一個概念,里面存放的都是棧幀,一個棧幀代表的就是一個函數的調用,在棧幀里面存放了函數的形參,函數的局部變量, 返回地址等,但是與C/C++的 一個重要區別是,C/C++里面有傳值以及傳址的區別,當傳的是一個對象時( 結構體也可以當成對象,其實就是對象~,只不過里面的方法默認都是public的,不信你可以試試,在結構體中加一個函數,編譯器也不會報錯,程序依舊運 行~~~),會將對象復到到棧中,而Java中只有基本類型才是傳值的,其他類型傳的都是引用,什么是引用,學過C/C++的就把引用當作指針理解 吧~~~,在這個基本架構圖中,可以看出JVM還定義了一個本地方法棧,本地方法棧是為Java調用本地方法【這些本地方法是由其他語言編寫的】服務的

 JVM內存堆布局圖解分析

上面的圖中看到的是JVM中棧有兩個,但是堆只有一個,每一個線程都有自已的線程棧【線程棧的大小可以通過設置JVM-xss參數進行配置,32位系統下,一般默認的大小是512K】,線程棧里面的數據屬于該線程私有,但是所有的線程都共享一個堆空間,堆中存放的是對象數據,什么是對象數據,排除法,排除基本類型以及引用類型以外的數據都將放在堆空間中,下面來具體分析一下堆空間...

在JVM中堆空間劃分如下圖所示

 JVM內存堆布局圖解分析

 

上圖中,刻畫了Java程序運行時的堆空間,可以簡述成如下2條

1.JVM中堆空間可以分成三個大區,新生代、老年代、永久代

2.新生代可以劃分為三個區,Eden區,兩個幸存區

在JVM運行時,可以通過配置以下參數改變整個JVM堆的配置比例

 
1.JVM運行時堆的大小

  -Xms堆的最小值

  -Xmx堆空間的最大值

2.新生代堆空間大小調整

  -XX:NewSize新生代的最小值

  -XX:MaxNewSize新生代的最大值

  -XX:NewRatio設置新生代與老年代在堆空間的大小

  -XX:SurvivorRatio新生代中Eden所占區域的大小

3.永久代大小調整

  -XX:MaxPermSize

4.其他

   -XX:MaxTenuringThreshold,設置將新生代對象轉到老年代時需要經過多少次垃圾回收,但是仍然沒有被回收

在上面的配置中,老年代所占空間的大小是由-XX:SurvivorRatio這個參數進行配置的,看完了上面的JVM堆空間分配圖,可能會奇怪,為啥新生代空間要劃分為三個區Eden及兩個Survivor區?有何用意?為什么要這么分?要理解這個問題,就得理解一下JVM的垃圾收集機制(復制算法也叫copy算法),步驟如下:

復制(Copying)算法
將內存平均分成A、B兩塊,算法過程:
1. 新生對象被分配到A塊中未使用的內存當中。當A塊的內存用完了, 把A塊的存活對象對象復制到B塊。
2. 清理A塊所有對象。
3. 新生對象被分配的B塊中未使用的內存當中。當B塊的內存用完了, 把B塊的存活對象對象復制到A塊。
4. 清理B塊所有對象。
5. goto 1。
優點:簡單高效。缺點:內存代價高,有效內存為占用內存的一半。
圖解說明如下所示:(圖中后觀是一個循環過程)
  JVM內存堆布局圖解分析
對復制算法進一步優化:使用Eden/S0/S1三個分區
平均分成A/B塊太浪費內存,采用Eden/S0/S1三個區更合理,空間比例為Eden:S0:S1==8:1:1,有效內存(即可分配新生對象的內存)是總內存的9/10。
算法過程:
1. Eden+S0可分配新生對象;
2. 對Eden+S0進行垃圾收集,存活對象復制到S1。清理Eden+S0。一次新生代GC結束。
3. Eden+S1可分配新生對象;
4. 對Eden+S1進行垃圾收集,存活對象復制到S0。清理Eden+S1。二次新生代GC結束。
5. goto 1。
 
默認Eden:S0:S1=8:1:1,因此,新生代中可以使用的內存空間大小占用新生代的9/10,那么有人就會問,為什么不直接分成兩個區,一個區占9/10,另一個區占1/10,這樣做的原因大概有以下幾種
1.S0與S1的區間明顯較小,有效新生代空間為Eden+S0/S1,因此有效空間就大,增加了內存使用率
2.有利于對象代的計算,當一個對象在S0/S1中達到設置的XX:MaxTenuringThreshold 值后,會將其分到老年代中,設想一下,如果沒有S0/S1,直接分成兩個區,該如何計算對象經過了多少次GC還沒被釋放,你可能會說,在對象里加一個計數 器記錄經過的GC次數,或者存在一張映射表記錄對象和GC次數的關系,是的,可以,但是這樣的話,會掃描整個新生代中的對象, 有了S0/S1我們就可以只掃描S0/S1區了~~~
 
GC實例分析,下次再分析~

來自:http://www.cnblogs.com/WJ5888/p/4374791.html
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!