1. 程式人生 > >垃圾收集器與記憶體分配策略(六)——記憶體分配與回收策略

垃圾收集器與記憶體分配策略(六)——記憶體分配與回收策略

物件的記憶體分配,往大方向上講,就是在堆上分配(但也可能經過JIT編譯後被拆散為標量型別並間接地棧上分配),物件主要分配在新生代的Eden區上,如果啟動了本地執行緒分配緩衝,將按執行緒優先在TLAB上分配。少數情況下也可能會直接分配在老年代中,分配的規則並不是百分之百固定的,其細節取決於當前使用的是哪一種垃圾收集器組合,還有虛擬機器中與記憶體相關的引數的設定。

(1)物件優先在Eden分配

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

虛擬機器提供了-XX:+PrintGCDetails這個收集器日誌引數,告訴虛擬機器在發生垃圾收集行為時列印記憶體回收日誌,並且在程序退出的時候輸出當前的記憶體各區域分配情況。

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

老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Monitor GC(並非絕對)。Major GC的速度一般會比Monitor GC慢10倍以上。

(2)大物件直接進入老年代

所謂的大物件是指,需要大量連續記憶體空間的Java物件,最典型的大物件就是那種很長的字串以及陣列。大物件對虛擬機器的記憶體分配來說就是一個壞訊息(尤其是朝生夕死的大物件),經常出現大物件容易導致記憶體還有不少空間時就提前出發垃圾收集以獲取足夠的連續空間來安置它們。

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

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

既然虛擬機器採用了分代收集的思想來管理記憶體,那麼記憶體回收時就必須能識別那些物件應放在新生代,那些物件應放在老年代中。為了做到這點,虛擬機器給每個物件定義了一個物件年齡計數器。如果物件在Eden出生並經過第一次Monitor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設為1。物件在Survivor區中每熬過一次Monitor GC,年齡就增加一歲,當它的年齡增加到一定程度(預設為15歲),就將會被晉升到老年代中。物件晉升老年代的年齡閥值可以通過引數-XX:MaxTenuringThreshold設定。

(4)動態物件年齡判定

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

(5)空間分配擔保

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

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

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

參考文件:《深入理解java虛擬機器》周志明著