文章來(lái)源:深入理解jvm空間分配擔(dān)保
JDK 6 Update 24在發(fā)生Minor GC之前刽宪,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生 代所有對(duì)象總空間瘦麸,如果這個(gè)條件成立只酥,那這一次Minor GC可以確保是安全的奕翔。如果不 成立从橘,則虛擬機(jī)會(huì)先查看-XX:HandlePromotionFailure參數(shù)的設(shè)置值是否允許擔(dān)保失敗 (Handle Promotion Failure)楼熄;如果允許,那會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否 大于歷次晉升到老年代對(duì)象的平均大小闸与,如果大于毙替,將嘗試進(jìn)行一次Minor GC,盡管這 次Minor GC是有風(fēng)險(xiǎn)的践樱;如果小于厂画,或者-XX:HandlePromotionFailure設(shè)置不允許冒險(xiǎn), 那這時(shí)就要改為進(jìn)行一次Full GC拷邢。 解釋一下“冒險(xiǎn)”是冒了什么風(fēng)險(xiǎn):前面提到過(guò)袱院,新生代使用復(fù)制收集算法,但為了內(nèi) 存利用率瞭稼,只使用其中一個(gè)Survivor空間來(lái)作為輪換備份忽洛,因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況——最極端的情況就是內(nèi)存回收后新生代中所有對(duì)象都存活,需要 老年代進(jìn)行分配擔(dān)保环肘,把Survivor無(wú)法容納的對(duì)象直接送入老年代欲虚,這與生活中貸款擔(dān)保 類似。老年代要進(jìn)行這樣的擔(dān)保悔雹,前提是老年代本身還有容納這些對(duì)象的剩余空間复哆,但一 共有多少對(duì)象會(huì)在這次回收中活下來(lái)在實(shí)際完成內(nèi)存回收之前是無(wú)法明確知道的,所以只 能取之前每一次回收晉升到老年代對(duì)象容量的平均大小作為經(jīng)驗(yàn)值荠商,與老年代的剩余空間 進(jìn)行比較寂恬,決定是否進(jìn)行Full GC來(lái)讓老年代騰出更多空間续誉。 取歷史平均值來(lái)比較其實(shí)仍然是一種賭概率的解決辦法莱没,也就是說(shuō)假如某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于歷史平均值的話酷鸦,依然會(huì)導(dǎo)致?lián)J∈味恪H绻霈F(xiàn)了擔(dān) 保失敗牙咏,那就只好老老實(shí)實(shí)地重新發(fā)起一次Full GC,這樣停頓時(shí)間就很長(zhǎng)了嘹裂。雖然擔(dān)保 失敗時(shí)繞的圈子是最大的妄壶,但通常情況下都還是會(huì)將-XX:HandlePromotionFailure開(kāi)關(guān)打 開(kāi),避免Full GC過(guò)于頻繁寄狼。參見(jiàn)代碼如下(JDK 6 Update 24)之前的 HotSpot運(yùn)行測(cè)試代碼丁寄。
private static final int _1MB = 1024 * 1024;
/*** VM參數(shù):-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-Handle- PromotionFailure */ @SuppressWarnings("unused")
public static void testHandlePromotion() {
byte[] allocation1, allocation2, allocation3, allocation4, allocation5, alloca-tion6, allocation7;
allocation1 = new byte[2 * _1MB];
allocation2 = new byte[2 * _1MB];
allocation3 = new byte[2 * _1MB];
allocation1 = null;
allocation4 = new byte[2 * _1MB];
allocation5 = new byte[2 * _1MB];
allocation6 = new byte[2 * _1MB];
allocation4 = null;
allocation5 = null;
allocation6 = null;
allocation7 = new byte[2 * _1MB];
}
以-XX:HandlePromotionFailure=false參數(shù)來(lái)運(yùn)行的結(jié)果:
[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs] [GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][Tenured: 4096K->4244K(10240K), 0.0042901 secs] 10474K->42
以-XX:HandlePromotionFailure=true參數(shù)來(lái)運(yùn)行的結(jié)果:
[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 s
在JDK 6 Update 24之后,這個(gè)測(cè)試結(jié)果就有了差異泊愧,-XX:HandlePromotionFailure參 數(shù)不會(huì)再影響到虛擬機(jī)的空間分配擔(dān)保策略伊磺,觀察OpenJDK中的源碼變化(參見(jiàn)如下代碼),雖然源碼中還定義了-XX:HandlePromotionFailure參數(shù)删咱,但是在實(shí)際虛擬機(jī)中已經(jīng) 不會(huì)再使用它屑埋。JDK 6 Update 24之后的規(guī)則變?yōu)橹灰夏甏倪B續(xù)空間大于新生代對(duì)象總 大小或者歷次晉升的平均大小,就會(huì)進(jìn)行Minor GC痰滋,否則將進(jìn)行Full GC摘能。
bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
// 老年代最大可用的連續(xù)空間
size_t available = max_contiguous_available();
// 每次晉升到老年代的平均大小
size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average();
// 老年代可用空間是否大于平均晉升大小,或者老年代可用空間是否大于當(dāng)此GC時(shí)新生代所有對(duì)象容量
bool res = (available >= av_promo) || (available >= max_promotion_in_bytes); return res; }