Java技術體系中的自動內存管理可以歸結爲自動化的解決了兩個問題:給對象分配內存和回收分配給對象的內存。前面講的是內存回收,這章講如何爲對象分配內存。
一、內存分配策略概述
對象的內存分分配主要是指堆上分配(也可棧上分配),對象主要分配在新生代Eden區,如果啓動了本地線程分配緩衝,則按照線程優先在TLAB上分配。少數情況下也會直接分配在老年代,分配的規則不固定,取決於垃圾回收器組合以及JVM中與內存相關參數的設置。
我們以Serial/Serial Old收集器爲例進行介紹。
二、內存分配策略
1.對象優先在Eden區分配
大多數情況下,對象在新生代的Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機將會發起一次Minor GC。
2.大對象直接進入老年代
大對象是指需要大量連續內存空間的Java對象,典型大對象有長字符串和數組,大對象對虛擬機的內存分配來說是一個壞消息,寫程序時還要避免創建一些朝生夕死的短命大對象,經常出現大對象容易導致內存還有不少空間時就提前觸發垃圾收集以獲取足夠的連續空間來安置他們。
虛擬機提供-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配,這樣避免再Eden及兩個Survivor區間發生大量的內存複製,這個參數只對Serial和ParNew兩款收集器有效,如果遇到必須使用此參數的場合,可以考慮ParNew加CMS的組合。
大對象直接在老年代進行分配的目的是避免在Eden區和兩個Survivor區之間發生大量的內存複製。
3.根據對象年齡判定進入老年代
虛擬機給每個對象定義了一個對象年齡計數器,如果對象在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且對象年齡設爲1,對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認爲15歲),就會被晉升到老年代中,可以通過-XX:MaxTenuringThreshold參數來設置年齡閾值。
4.動態對象年齡判斷
爲了能更好的適應不同程序的內存情況,虛擬機不是永遠的要求對象的年齡必須達到閾值才能晉升老年代;如果在Survivor區中的相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或者等於該年齡的對象就可以直接進入老年代,無須等到-XX:MaxTenuringThreshold 中要求的年齡。
5.空間分配擔保
關於分配擔保機制JDK6前後有差別的。
JDK6之前
當發生Minor GC時,JVM會首先檢查老年代最大的可用連續空間是否大於新生代所有對象的總和,如果大於,那麼這次Minor GC是安全的,如果不大於的話,JVM就需要判斷HandlePromotionFailure是否允許空間分配擔保。
如果允許擔保失敗,那麼JVM會繼續檢查老年代最大的可用連續空間是否大於歷次晉升到老年代的對象的平均大小:
- 如果大於,則正常進行一次YGC,儘管有風險(因爲判斷的是平均大小,有可能這次的晉升對象比平均值大很多);
- 如果小於,或者HandlePromotionFailure設置不允許空間分配擔保,這時要進行一次FGC。
對以上的步驟歸納一下,先看老年代的可用空間能否容下新生代的所有對象,不能的話看是否開啓了分配擔保機制,允許就先執行Minor GC,否則直接進行Full GC。大部分情況下還是會將HandlePromotionFailure開啓分配擔保,避免頻繁Full GC。
JDK6後
HandlePromotionFailure不再影響到虛擬機的空間分配擔保策略,變爲只要老年代的連續空間大於新生代對象總大小或歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。
三、概念充電
Minor GC和Major GC/Full GC:
- 新生代GC(Minor GC:縮寫YGC,指發生在新生代的垃圾回收動作,因爲Java對象大多都具備朝生夕滅的特性,所以Monir GC非常頻繁,一般回收速度也比較快。
- 老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,經常會伴隨至少一次的Minor GC(但並非絕對的,在Parallel Scavenge)收集器的收集策略裏就有直接進行Major GC的策略選擇過程。Major GC的速度一般比Major GC慢10倍以上。
參考資料: