深入理解Java虛擬機器筆記之六記憶體分配與回收策略

zhumeilu發表於2019-01-18

物件的記憶體分配,物件主要分配在新生代的Eden區上,如果啟動了本地執行緒分配緩衝,將按執行緒優先在TLAB上分配。少數情況下也可能會直接分配在老年代中,分配的規則並不是百分百固定的,其細節取決於當前使用的是哪一宗垃圾收集器組合,還有虛擬機器中與記憶體相關的引數的設定。

物件優先在Eden分配

大多數情況下,物件在新生代的Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機器將發起一次Minor GC。

新生代GC(Minor GC):指傳送在新生代的垃圾收集動作,因為Java物件大多數都具備朝生夕死的特性,所以Minor GC非常頻繁,一般回收速度也較快。

老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但非絕對的,在Parallel Scavenge收集器的收集策略裡就有直接進行Major GC的策略選擇過程)。Major GC的速度一般會比Minor GC慢10倍以上。

大物件直接進入老年代

大物件指的是需要大量連續記憶體空間的Java物件,最典型的大物件就是那種很長的字串以及陣列。

經常出現大物件容易導致記憶體還有不少空間時就提前觸發垃圾收集以獲取足夠的連續空間來安置它們。

虛擬機器提供了一個-XX:PretenureSizeThreshold引數,令大於這個設定值的物件直接在老年代分配。這樣做的目的是避免在Eden區以及兩個 Survivor區之間發生大量的記憶體複製。

PretenureSizcThreshold 引數只對Serial和ParNew兩款收集器有效,Parallel Scavenge收集器不認識這個引數,Parallel Scavenge收集器一般並不需要設定。如果遇到必須使用此引數的場合,可以考慮ParNew加CMS的收集器組合。

長期存活的物件將進入老年代

虛擬機器給每個物件定義了一個年齡計數器,如果物件在Eden出生並經歷過第一次Minor GC後仍然存活,並且能被Survivor區容納的話,將會被移動到Survivor空間中,並且物件年齡設為1。物件在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(預設15歲),就將會被晉升到老年代中。物件晉升老年代的年齡閾值,可通過引數-XX:MaxTenuringThreshold設定

動態物件年齡判定

為了能更好地適應不同程式的記憶體狀況,虛擬機器並不是永遠地要求物件的年齡必須達到 字MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的物件就可以直接進大老年代,無須等到MaxTenuringThreshold中要求的年齡。

空間分配擔保

在發生MinorGC之前,“虛擬機器會先檢查老年代最大可用的連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立,則虛擬機器會檢視HandlePromotionFailure設定值是否允許擔保失敗。如果允許,那麼 會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC是有風險的,如果小於,或者 HandlePromotionFailure設定不允許冒險,那這時也要改為進行一次Full GG。

下面解釋一下“冒險”是冒了什麼風險,前面提到過,.新生代使用複製收集演算法,但為了記憶體利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量物件在一 次MinorGC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有物件都存活),就需要老年代進行分配擔保,把Survivor無法容納的物件直接進人老年代。與生活中的貸款擔保類似,老年代要進行這樣的擔保,前提是老年代本身還有容納這些物件的剩餘空間,一共有多少物件會活下來在實際完成記憶體回收之前是無法明確知道的,所以只好取之前每次回收晉升到老年代物件容量的平均大小值作為經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。

取平均值進行比較其實仍然是一種動態概率的手段,也就是說,如果某次Minor GC存 活後的物件突增,遠遠高於平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次Full GC。雖然擔保失敗時繞的圈子是最大的,但大部分情況下都還是會將HandlePromotionFailure 開關開啟,避免Full GC過於頻繁。

在JDK1.6之後,HandlePromotionFailure引數不會再影響到虛擬機器的空間分配擔保策略,之後的規則變為只要老年代的連續空間大於新生代物件總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。

相關文章