發(fā)生YGC時(shí)的一些細(xì)節(jié)

簡書 占小狼
轉(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é)中迷失了自己...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • System.gc整理 System.gc()源碼public static void gc() { Runtim...
    andersonoy閱讀 3,147評(píng)論 0 1
  • 在高并發(fā)下,Java程序的GC問題屬于很典型的一類問題,帶來的影響往往會(huì)被進(jìn)一步放大。不管是「GC頻率過快」還是「...
    AI喬治閱讀 1,319評(píng)論 0 12
  • JVM架構(gòu) 當(dāng)一個(gè)程序啟動(dòng)之前,它的class會(huì)被類裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,845評(píng)論 0 7
  • 一. 前提 最近由于系統(tǒng)業(yè)務(wù)量比較大,從生產(chǎn)的GC日志(結(jié)合Pinpoint)來看,需要對(duì)部分系統(tǒng)進(jìn)行GC調(diào)優(yōu)。但...
    Java_蘇先生閱讀 378評(píng)論 0 2
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,674評(píng)論 2 7

友情鏈接更多精彩內(nèi)容