簡書 占小狼
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!
周末抽空把YGC的源碼實(shí)現(xiàn)重新看了一遍,發(fā)現(xiàn)細(xì)節(jié)遠(yuǎn)比知道的多...
首先要知道,什么情況會(huì)導(dǎo)致YGC的發(fā)生?最常見的情況是在年輕代分配內(nèi)存時(shí),出現(xiàn)空間不足,這里的內(nèi)存分配,有可能是TLAB,也有可能是一個(gè)對(duì)象(該對(duì)象在TLAB中放不下,但虛擬機(jī)不想重新申請(qǐng)TLAB,就在Eden區(qū)分配)
1、如果觸發(fā)的YGC順利執(zhí)行完,期間沒有發(fā)生任何問題,垃圾回收完成后,正常的分配內(nèi)存。
2、如果YGC剛要開始執(zhí)行,卻不幸的發(fā)生了JNI的GC locker,本次的YGC會(huì)被放棄,如果是給對(duì)象分配內(nèi)存,會(huì)在老年代中直接分配內(nèi)存,如果是TLAB的話,就要等JNI結(jié)束了。
3、如果沒有JNI的干擾,在YGC過程中,對(duì)象年紀(jì)達(dá)到閾值,正常晉升,或to空間不足,對(duì)象提前晉升,但老年代又沒這么多空間容納晉升上來的對(duì)象,這時(shí)會(huì)發(fā)生“promotion failed”,而且eden和from區(qū)的空間沒辦法清空, 把from區(qū)和to區(qū)進(jìn)行swap,所以當(dāng)前eden和from的使用率都是接近100%的,如果當(dāng)前是給對(duì)象(非TLAB)申請(qǐng)內(nèi)存,會(huì)繼續(xù)觸發(fā)一次老年代的回收動(dòng)作,下面是一個(gè)例子:
/**
* -Xmx20m -Xms20m -Xmn14m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
*-XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75
*-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC
*/
public class JVM {
private static final int _1MB = 1024 * 1024;
private static final int _1K = 1024;
public static void main(String[] args) throws Exception {
byte[][] arr = new byte[10000][];
for (int i = 0; i< 1200; i++) {
arr[i] = new byte[10* _1K];
}
System.in.read();
}
}
把年輕代設(shè)置的大點(diǎn),制造“promotion failed”,下面是gc日志:

1、“promotion failed” 如期到來。
2、對(duì)老年代進(jìn)行了一次回收,這次回收有兩種方式,一種是compact,另一種是mark-sweep,顯然第一種會(huì)進(jìn)行內(nèi)存的壓縮操作,第二種只進(jìn)行標(biāo)記清除。到底會(huì)使用哪一種,會(huì)進(jìn)行如下判斷:

其中UseCMSCompactAtFullCollection默認(rèn)是開啟的,而且CMSFullGCsBeforeCompaction默認(rèn)是0,所以如果沒有重新設(shè)置該參數(shù)的話,按理說,每次都是會(huì)進(jìn)行compact操作,如果CMSFullGCsBeforeCompaction被設(shè)置成一個(gè)大于0的值,還有其它條件可以導(dǎo)致compact,一個(gè)是如System.gc,另一個(gè)是發(fā)生了promotion failed,還有其它等等。
本例子中雖然使用了-XX:+UseConcMarkSweepGC,但是不會(huì)使用并發(fā)的CMS算法,如果當(dāng)前CMS的background collect已經(jīng)開始執(zhí)行,當(dāng)前GC線程會(huì)搶過執(zhí)行權(quán),并記錄“concurrent mode failed”。
如果需要compact,采用單線程的Serial GC進(jìn)行回收,該算法實(shí)現(xiàn)在genMarkSweep.cpp。
如果不需要compact,則執(zhí)行一個(gè)標(biāo)記-清除的過程,實(shí)現(xiàn)在CMSCollector::collect_in_foreground中。
3、同樣對(duì)永久帶也來了一次回收
從這個(gè)打印出來的日志可以發(fā)現(xiàn),本次的YGC是分配對(duì)象時(shí)觸發(fā)的,而不是TLAB(因?yàn)樵贘VM運(yùn)行過程中,會(huì)動(dòng)態(tài)調(diào)整TLAB的大小和最大浪費(fèi)空間),雖然日志中沒有FULL GC的字樣,其實(shí)執(zhí)行的就是一次full gc過程。
本來我一直納悶為啥TLAB的分配,會(huì)導(dǎo)致老年代的回收,因?yàn)槿绻荰LAB的話,老年代的should_allocate方法實(shí)現(xiàn)如下:
virtual bool should_allocate(size_t word_size, bool is_tlab) {
bool result = false;
size_t overflow_limit = (size_t)1 << (BitsPerSize_t - LogHeapWordSize);
if (!is_tlab || supports_tlab_allocation()) {
result = (word_size > 0) && (word_size < overflow_limit);
}
return result;
}
其中is_tlab為true,supports_tlab_allocation()為false,所以不會(huì)繼續(xù)對(duì)老年代進(jìn)行回收。
漸行漸遠(yuǎn),細(xì)節(jié)遠(yuǎn)不止這些,不要在細(xì)節(jié)中迷失了自己...