Java JVM 內存分配與回收策略

對象的內存分配,往大的方向講。就是在堆上分配(但也可能經過JIT編譯後被拆散爲標量類型餅間接棧上分配),對象主要分配在新生代的Eden 區上,如果啓動 本地線程分配緩衝(TLAB),將按照線程有限在TLAB上分配。在少數情況下也可能直接分配在老年代中,分配規則並不是百分比固定,其細節取決於當前使用的是哪一種垃圾收集器組合,還有虛擬機中與內存相關得參數設置。

新生代GC和老年代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倍以上。

 

下面筆記基於serial ,serial old

優先分配eden

大多數情況下,對象在新生代eden區中分配,當eden區沒有足夠空間進行分配時,虛擬機將發起一次minor GC 。

大對象直接進入老年代

所謂大對象是指,需要大量連續內存空間的Java對象,最典型的大對蝦就是那種很長的字符串以及數組,大對蝦對虛擬機的內存分配來講就是一個壞消息(比遇到一個大對蝦更加壞的消息就是遇到一羣“朝生夕滅”的“短命大對象”,寫程序的時候應當避免),經常出現大對蝦導致內存還有不少空間時提前觸發來及收集以獲取足夠的連續空間來“安置”他們。

長期存活的對象將進入老年代

如果對象在eden 出生並經過第一次Minor GC 後仍然存活,並且能被survivor 容納的話,將被移動到Survivor空間中,並且對象年齡設置爲1 ,。對象在Survivor 區中每“熬過”一次minorGC 年齡就增加1歲,當他的年齡增加到一定成都(默認爲15歲),將會被晉升到來年的中。

動態對象年齡判定

爲了能夠更好適應不同程序的內存狀況,虛擬機並不是永遠德要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡的所有對象大小的總和大於Survivor 空間的一半,年齡大於或者等於該年齡的對象就可以直接進入老年代,無需等到MaxTenuringThreshold中要求的年齡

空間分配擔保

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

前面提到過,新生代使用複製算法,但爲了內存利用率,只使用一個Survivor 空間來作爲輪換備份,因此當出現大量對象在Minor GC後仍然存活的情況(最極端的情況就是內存回收後新生代中所有對象都存活),就需要老年代進行分配擔保,把Survivor無法容納的對象直接進入老年代。與生活中的貸款擔保類似,老年代要進行這樣的擔保,前提時老年代本身還有容納這些對象的剩餘空間,一共有多少對象活下來在實際完成內存回收之前時無法明確知道的,所以只好取之前每次回收晉升到老年代對象容量的平均值作爲經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC 來讓老年代騰出更多空間。取平均值進行比較仍然時一種動態概率的手段,也就是說,如果某次Minor GC 存活後的對象突增,遠遠高於 平均值的話,依然後導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次 Full GC ,雖然擔保失敗時繞的圈子是最大的,但大部分情況下都還是會將HandlePromotionFailure開關打開,避Full GC 過於頻繁。

 

特殊說明

JDK Update 24之後,HandlePromotionFailure參數不會影響到虛擬機的空間分配擔保策略,而是只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC ,否則就進行 Full GC 。

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