1. 程式人生 > >【深入Java虛擬機器】之記憶體區域詳解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen)

【深入Java虛擬機器】之記憶體區域詳解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen)

1.記憶體區域劃分

限定商用虛擬機器基本都採用分代收集演算法進行垃圾回收。根據物件的生命週期的不同將記憶體劃分為幾塊,然後根據各塊的特點採用最適當的收集演算法。大批物件死去、少量物件存活的,使用複製演算法,複製成本低;物件存活率高、沒有額外空間進行分配擔保的,採用標記-清除演算法或者標記-整理演算法。

從上面的圖可以看出, JVM區域總體分兩類,heap區和非heap區。 

1.heap區又分為: 

  • - Eden Space(伊甸園)、 
  • - Survivor Space(倖存者區)、 
  • - Old Gen(老年代)。

2.非heap區又分: 

  • - Code Cache(程式碼快取區); 
  • - Perm Gen(永久代); 
  • - Jvm Stack(java虛擬機器棧); 
  • - Local Method Statck(本地方法棧);

關於java堆,新生代,老年代,Eden空間,From Survivor空間,To Survivor空間
java程序執行過程中建立的物件存放在堆中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區域:Eden、From Survivor、To Survivor。
堆的記憶體模型大致為:
img


預設的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過引數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。
老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。
預設的,Edem : from : to = 8 : 1 : 1 ( 可以通過引數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為物件服務,所以無論什麼時候,總是有一塊 Survivor 區域是空閒著的

因此,新生代實際可用的記憶體空間為 9/10 ( 即90% )的新生代空間

新生代是 GC 收集垃圾的頻繁區域。
當物件在 Eden ( 包括一個 Survivor 區域,這裡假設是 from 區域 ) 出生後,在經過一次 Minor GC 後,如果物件還存活,並且能夠被另外一塊 Survivor 區域所容納
( 上面已經假設為 from 區域,這裡應為 to 區域,即 to 區域有足夠的記憶體空間來儲存 Eden 和 from 區域中存活的物件 ),則使用複製演算法將這些仍然還存活的物件複製到另外一塊 Survivor 區域 ( 即 to 區域 ) 中,然後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),並且將這些物件的年齡設定為1,以後物件在 Survivor 區每熬過一次 Minor GC,就將物件的年齡 + 1,當物件的年齡達到某個值時 ( 預設是 15 歲,可以通過引數 -XX:MaxTenuringThreshold 來設定 ),這些物件就會成為老年代。
但這也不是一定的,對於一些較大的物件 ( 即需要分配一塊較大的連續記憶體空間 ) 則是直接進入到老年代。

From Survivor區域與To Survivor區域是交替切換空間,在同一時間內兩者中只有一個不為空

2.記憶體區域介紹

1.年輕代:

HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to)。預設比例為8:1,為啥預設會是這個比例,接下來我們會聊到。一般情況下,新建立的物件都會被分配到Eden區(一些大物件特殊處理),這些物件經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區。物件在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。

因為年輕代中的物件基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收演算法使用的是複製演算法,複製演算法的基本思想就是將記憶體分為兩塊,每次只用其中一塊,當這一塊記憶體用完,就將還活著的物件複製到另外一塊上面。複製演算法不會產生記憶體碎片。

在GC開始的時候,物件只會存在於Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的物件都會被複制到“To”,而在“From”區中,仍存活的物件會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設定)的物件會被移動到年老代中,沒有達到閾值的物件會被複制到“To”區域。經過這次GC後,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將所有物件移動到年老代中。

有關年輕代的JVM引數

1)-XX:NewSize和-XX:MaxNewSize

用於設定年輕代的大小,建議設為整個堆大小的1/3或者1/4,兩個值設為一樣大。

2)-XX:SurvivorRatio

用於設定Eden和其中一個Survivor的比值,這個值也比較重要。

3)-XX:+PrintTenuringDistribution

這個引數用於顯示每次Minor GC時Survivor區中各個年齡段的物件的大小。

4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold

用於設定晉升到老年代的物件年齡的最小值和最大值,每個物件在堅持過一次Minor GC之後,年齡就加1。

 

 

2.old老年代

老年代,用於存放新生代中經過多次垃圾回收仍然存活的物件,也有可能是新生代分配不了記憶體的大物件會直接進入老年代。經過多次垃圾回收都沒有被回收的物件,這些物件的年代已經足夠old了,就會放入到老年代。

當老年代被放滿的之後,虛擬機器會進行垃圾回收,稱之為Major GC。由於Major GC除併發GC外均需對整個堆進行掃描和回收,因此又稱為Full GC。

heap區即堆記憶體,整個堆大小=年輕代大小 + 老年代大小。堆記憶體預設為實體記憶體的1/64(<1GB);預設空餘堆記憶體小於40%時,JVM就會增大堆直到-Xmx的最大限制,可以通過MinHeapFreeRatio引數進行調整;預設空餘堆記憶體大於70%時,JVM會減少堆直到-Xms的最小限制,可以通過MaxHeapFreeRatio引數進行調整。

3.Code Cache程式碼快取區

 

它主要用於存放JIT所編譯的熱點程式碼。CodeCache程式碼緩衝區的大小在client模式下預設最大是32m,在server模式下預設是48m,這個值也是可以設定的,它所對應的JVM引數為ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通過如下的方式來為Java程式設定。

 

-XX:ReservedCodeCacheSize=128m

 

CodeCache快取區是可能被充滿的,當CodeCache滿時,後臺會收到CodeCache is full的警告資訊,如下所示: 
“CompilerThread0” java.lang.OutOfMemoryError: requested 2854248 bytes for Chunk::new. Out of swap space?

 

4.Perm Gen(永久代) (JDK1.8之後被元空間替代)

Perm Gen全稱是Permanent Generation space,稱之為永久代,其實指的就是這個方法區。不過方法區和“PermGen space”又有著本質的區別。前者是 JVM 的規範,而後者則是 JVM 規範的一種實現,並且只有 HotSpot 才有 “PermGen space”,而對於其他型別的虛擬機器,如 JRockit(Oracle)、J9(IBM) 並沒有“PermGen space”。

由於方法區主要儲存類的相關資訊,Class在被Load進入這個區域後,如果應用程式LOAD很多Class的話,就很可能會出現PermGen space錯誤,比如對於動態生成類的情況比較容易出現永久代的記憶體溢位。它的預設大小為實體記憶體的1/64。

參考:

https://www.cnblogs.com/haitaofeiyang/p/8392268.html
https://blog.csdn.net/Muyundefeng/article/details/72667863