JVM垃圾收集(四) 堆上分區分配與回收

1 堆分區

1.1 新生代(Young Generation)和老年代(Tenured Generation)

JVM根據對象生存的特點,將對象劃分爲新生代和老年代。大部分對象在JVM內存中處是新生代,他們有一個特點,“朝生夕死”,即他們的生命週期很短,伴隨着一次GC可能就會被回收掉。相對於新生代來說,老年代就是相對“穩定”的一些對象。他們被JVM“認爲”是可以長期存活的對象。一般來說,晉級爲老年代的對象都是經過了一定次數GC存活下來的對象,當然也有可能是直接分配在老年代中的對象。

1.2  EdenSurvivor

在垃圾回收算法中,有一個算法稱爲複製算法。其基本思想是把內存劃分爲相等大小的兩個部分,內存分配僅在其中一個區域進行,當發生GC時,把一個區域中存活的對象複製到另外一個區域中,然後對這個區域進行整個GC,這樣就保證了內存連續性,減少了內存碎片的可能。然而這種方式卻犧牲了一半的內存空間。因此就出現了Eden區與Survivor區的概念。

他們是針對新生對象會很快消亡的特點,把新生代區域劃分爲三個空間一個較大的Eden區,和兩個相等的大小的From Survivor和To Survivor區。他們的默認大小比值爲8:1,即如果新生代大小爲10MB的話,Eden區大小爲8MB,from和to的空間各爲1MB。每次內存分配的時候,在Eden和一個1個survivor區域中分配(9MB大小),GC時把存活的對象複製到另一個survivor區域中,清理之前的9MB空間。當survivor空間不足時,啓動老年代內存擔保。

2 內存分配與回收策略

2.1對象優先在Eden分配

大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。對於這種情況,也有特例,當遇到大對象的時候,就要在老年代中分配。所謂的大對象指的是那種很長的字符串以及數組。大對象的分配對虛擬機來說是一個壞消息,經常出現大對象容易導致內存還有不少空間而不得不觸發一次垃圾收集來獲取連續的空間“安置”他們。

虛擬機提供了一個參數–XX:PertenureSizeThreshold,令大於這個設置值的對象直接在老年代

中分配,這樣就避免了在Eden區及兩個Survivor 區中發生大量的內存複製。

2.2 年齡增加與升級

虛擬機採用了分代思想去管理對象,那就應該識別出來哪些對象放在新生代,哪些對象放在老年代。爲了做到這點,虛擬機爲每個對象設置了一個Age計數器,如果對象在一個MinorGC過後再仍然存活,就將被移到survivor區中,然後age+1。對象每經歷一次Minor GC age就會增加1歲,當達到某個閾值的時候(默認爲15歲),就會晉升到老年代。可以通過設置參數-XX:MaxTenuringThreshold來設置晉升的年齡。

然而對於虛擬機來說,並不是一定要達到這個要求才能晉升爲老年代。還有一種情況就是某個年齡的對象大小的總和大於survivor區的一半時,年齡大於或等於這個年齡的對象都將進入到老年區。

2.3 內存分配擔保

MinorGC之前,虛擬機會檢驗老年代最大可用連續空間是否大於新生代所有對象的總和,如果這個條件成立,那麼MinorGC是安全的,這是一種基於最壞情況的考慮,就是新生代所有的對象都存活下來進入到老年代中。

如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那麼會檢查老年代最大可用連續空間是否大於歷次晉升老年代的對象大小的平均值。如果大於,則嘗試進行一次MinorGC,如果小於或者HandlePromotionFailure設置不允許,就要進行一次FullGC。

這裏需要解釋一下爲什麼要這麼做,複製算法爲了提高內存使用率,只有一個survivor區域作爲輪換備份,如果出現大量對象經過一次MinorGC以後仍然存活的情況,survivor空間不夠就要申請老年代的內存擔保。這個擔保是可能失敗的,因爲老年代中也許沒有足夠的空間。然後這種檢測又發生在MinorGC之前,這個過程是無法知道有多少對象需要進入老年代的,所以就取一個歷史平均值。

3 方法區的回收

對於方法區,我們會成爲“永久代”,因爲虛擬機規範中要求這個區域可以不發生垃圾回收,而且這個與堆中相比,這個區域的回收的性價比也不那麼高。遠遠低於堆的70~95%。

永久代的垃圾收集主要兩個部分內容:廢棄常量和無用的類。

3.1回收常量

回收廢棄常量與Java堆中的收集對象很相似。以常量池字面值爲例,常量池中有字符串abc,但是當前系統中沒有一個String類型的變量叫abc,這個abc可以被認爲是垃圾了。這個時候如果發生垃圾回收,就會把abc從常量池中清理出去

3.2 回收類

相對於無用常量的判斷,對於無用的類型的判斷就要稍微複雜一點。類需要同時滿足3個條件才能被判定爲”無用的類”。

(1)該類的所有實例都已經被回收,也就是Java堆中不包括任何一個該類的實例

(2)加載該類的classloader已經被回收

(3)該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。

滿足以上三個條件,只能說該類可能被回收,而並不是沒用了就一定被回收。在大量使用反射、動態代理、CGlib等字節碼框架、動態生成JSP以及OSGI這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章