JVM之CMSGC觸發(fā)

概述

最近一直迷惑CMS GC觸發(fā)有那些情況,專(zhuān)門(mén)去研究了一下CMS Thread 的源碼。廢話不說(shuō)了,讓我們開(kāi)始探究之旅。

ConcurrentMarkSweepThread

在啟動(dòng)JVM虛擬機(jī)時(shí),進(jìn)行各種初始化操作,其中就包括了GC線程的初始化,CMS GC線程初始化主要是通過(guò) ConcurrentMarkSweepThread(簡(jiǎn)稱(chēng)CMSThread) 類(lèi),下面具體研究一下該類(lèi)的構(gòu)造函數(shù),源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepThread.cpp

ConcurrentMarkSweepThread::ConcurrentMarkSweepThread(CMSCollector* collector)
  : ConcurrentGCThread() {
  //UseConcMarkSweepGC為true
  assert(UseConcMarkSweepGC,  "UseConcMarkSweepGC should be set");
  assert(_cmst == NULL, "CMS thread already created");
  _cmst = this;
  assert(_collector == NULL, "Collector already set");
  _collector = collector;
  //設(shè)置線程名字
  set_name("Concurrent Mark-Sweep GC Thread");

  if (os::create_thread(this, os::cgc_thread)) {
    int native_prio;
    //UseCriticalCMSThreadPriority默認(rèn)為false,如果配置為true,VMThread 可能不會(huì)獲得CPU
    if (UseCriticalCMSThreadPriority) {
      native_prio = os::java_to_os_priority[CriticalPriority];
    } else {
      native_prio = os::java_to_os_priority[NearMaxPriority];
    }
    os::set_native_priority(this, native_prio);

    if (!DisableStartThread) {
      os::start_thread(this);
    }
  }
  _sltMonitor = SLT_lock;
  assert(!CMSIncrementalMode || icms_is_enabled(), "Error");
}

上面是 CMSThread 線程的初始化,主要是設(shè)置線程名稱(chēng)以及線程的優(yōu)先級(jí)等。
與JAVA的線程類(lèi)似,在 run 方法中,定義了 CMSThread 線程的工作,接下來(lái),我們分析CMSThread線程的 run 方法。

void ConcurrentMarkSweepThread::run() {
 ...............................省略....................................
  // Wait until Universe::is_fully_initialized()
  {
    CMSLoopCountWarn loopX("CMS::run", "waiting for "
                           "Universe::is_fully_initialized()", 2);
    MutexLockerEx x(CGC_lock, true);
    set_CMS_flag(CMS_cms_wants_token);
    // 等待堆初始化完成,而且其他的初始化工作完成
    while (!is_init_completed() && !Universe::is_fully_initialized() &&
           !_should_terminate) {
      CGC_lock->wait(true, 200);
      loopX.tick();
    }
    //等待surrogate locker thread的執(zhí)行
    CMSLoopCountWarn loopY("CMS::run", "waiting for SLT installation", 2);
    while (_slt == NULL && !_should_terminate) {
      CGC_lock->wait(true, 200);
      loopY.tick();
    }
    clear_CMS_flag(CMS_cms_wants_token);
  }

  while (!_should_terminate) {
    //如果沒(méi)有觸發(fā)CMS GC,CMSThread線程阻塞
    sleepBeforeNextCycle();
    if (_should_terminate) break;
    GCCause::Cause cause = _collector->_full_gc_requested ?
      _collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
    _collector->collect_in_background(false, cause);
  }
 ...............................省略....................................

在上面的代碼中,等待各種初始化操作的完成,然后通過(guò)while循環(huán),檢測(cè)是否有觸發(fā)GC,當(dāng)觸發(fā)GC時(shí),調(diào)用 collect_in_background 進(jìn)行GC操作。關(guān)于CMS的GC詳情以后進(jìn)行分析,我們本次主要分析CMS GC觸發(fā)的原因。從上面的代碼中可以看出,在 sleepBeforeNextCycle 方法中檢查是否要進(jìn)行GC。

void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {
  while (!_should_terminate) {
    if (CMSIncrementalMode) {//CMS增量模式回收,默認(rèn)為false
      icms_wait();
      return;
    } else {
      //類(lèi)似與Java中的wait(2000),CMSWaitDuration默認(rèn)2000ms
      wait_on_cms_lock(CMSWaitDuration);
    }
    // 檢查是否要進(jìn)行CMS GC
    if (_collector->shouldConcurrentCollect()) {
      return;
    }
  }
}

上面的代碼中,判斷CMSIncrementalMode是否為T(mén)rue,如果為true,執(zhí)行 icms_wait 方法,如果為false,讓線程阻塞2000ms,當(dāng)阻塞時(shí)間超時(shí)以后,調(diào)用shouldConcurrentCollect 方法檢查是否需要執(zhí)行CMS GC。

CMS GC觸發(fā)的情況

在上面的小節(jié)中,我們分析到 CMSThread 調(diào)用 shouldConcurrentCollect 方法來(lái)判斷是否觸發(fā)CMS GC,下面我們分析一下 shouldConcurrentCollect 內(nèi)部的具體實(shí)現(xiàn),該方法篇幅過(guò)長(zhǎng),我們將將源碼分段講解,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepGeneration.cpp

第一部分
//有FullGC的請(qǐng)求
if (_full_gc_requested) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print_cr("CMSCollector: collect because of explicit "
                             " gc request (or gc_locker)");
    }
    return true;
  }

如果有FullGC的請(qǐng)求,就觸發(fā)一次GC(比如調(diào)用System.gc()觸發(fā)GC)

第二部分
//UseCMSInitiatingOccupancyOnly默認(rèn)false
//UseCMSInitiatingOccupancyOnly為false,會(huì)動(dòng)態(tài)計(jì)算閾值
//預(yù)計(jì)完成CMS回收所需要的時(shí)間小于預(yù)計(jì)的老年代填滿的時(shí)間,則進(jìn)行回收。
if (!UseCMSInitiatingOccupancyOnly) {
   if (stats().valid()) {
     if (stats().time_until_cms_start() == 0.0) {
       return true;
     }
   } else {
     //_bootstrap_occupancy默認(rèn)50%
     if (_cmsGen->occupancy() >= _bootstrap_occupancy) {
       if (Verbose && PrintGCDetails) {
         gclog_or_tty->print_cr(
           " CMSCollector: collect for bootstrapping statistics:"
           " occupancy = %f, boot occupancy = %f", _cmsGen->occupancy(),
           _bootstrap_occupancy);
       }
       return true;
     }
   }
 }

如果預(yù)計(jì)完成CMS回收所需要的時(shí)間小于預(yù)計(jì)的老年代填滿的時(shí)間,則進(jìn)行回收。

第三部分
 if (_cmsGen->should_concurrent_collect()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print_cr("CMS old gen initiated");
    }
    return true;
  }

在上面的代碼種可以看出調(diào)用 should_concurrent_collect 方法判斷,是否觸發(fā)GC,深入到該方法中,去了解具體的實(shí)現(xiàn)。

bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const {
  //內(nèi)存使用率大于初始化參數(shù)
  if (occupancy() > initiating_occupancy()) {
    if (PrintGCDetails && Verbose) {
      gclog_or_tty->print(" %s: collect because of occupancy %f / %f  ",
        short_name(), occupancy(), initiating_occupancy());
    }
    return true;
  }
  // UseCMSInitiatingOccupancyOnly 為true
  if (UseCMSInitiatingOccupancyOnly) {
    return false;
  }
  //在進(jìn)行堆擴(kuò)容
  if (expansion_cause() == CMSExpansionCause::_satisfy_allocation) {
    if (PrintGCDetails && Verbose) {
      gclog_or_tty->print(" %s: collect because expanded for allocation ",
        short_name());
    }
    return true;
  }
  if (_cmsSpace->should_concurrent_collect()) {
    if (PrintGCDetails && Verbose) {
      gclog_or_tty->print(" %s: collect because cmsSpace says so ",
        short_name());
    }
    return true;
  }
  return false;
}

在上面的代碼中,首先判斷老年代內(nèi)存使用率是否大于初始化參數(shù),如果為true,則觸發(fā)GC,如果為false,且UseCMSInitiatingOccupancyOnly 為true,則返回false。
接下來(lái)判斷是否在進(jìn)行堆擴(kuò)容,如果為true,則觸發(fā)GC,如果為false,則判斷CompactibleFreeListSpace是否需要進(jìn)行GC。

第四部分
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  assert(gch->collector_policy()->is_two_generation_policy(),
         "You may want to check the correctness of the following");
 //判斷年輕代存活對(duì)象晉升失敗
  if (gch->incremental_collection_will_fail(true /* consult_young */)) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print("CMSCollector: collect because incremental collection will fail ");
    }
    return true;
  }

在上面的代碼中,判斷年輕代存活的對(duì)象是否可能會(huì)失敗,如果失敗,觸發(fā)GC。查看 incremental_collection_will_fail 的具體實(shí)現(xiàn),源碼地址:hotspot\src\share\vm\memory\genCollectedHeap.cpp

  bool incremental_collection_will_fail(bool consult_young) {
  
    assert(heap()->collector_policy()->is_two_generation_policy(),
           "the following definition may not be suitable for an n(>2)-generation system");
    return incremental_collection_failed() ||
           (consult_young && !get_gen(0)->collection_attempt_is_safe());
  }

在上面的代碼中,調(diào)用 incremental_collection_failed 方法判斷之前是否晉升失敗,默認(rèn)為false,同時(shí)調(diào)用年輕代的 collection_attempt_is_safe 方法,判斷本次晉升是否失敗,根據(jù)或操作的結(jié)果判斷是否進(jìn)行GC,深入查看 collection_attempt_is_safe
源碼,源碼地址:hotspot\src\share\vm\memory\defNewGeneration.cpp

bool DefNewGeneration::collection_attempt_is_safe() {
  //to空間是否為空
  if (!to()->is_empty()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: to is not empty :: ");
    }
    return false;
  }
  //下一個(gè)內(nèi)存管理器為空
  if (_next_gen == NULL) {
    GenCollectedHeap* gch = GenCollectedHeap::heap();
    _next_gen = gch->next_gen(this);
    assert(_next_gen != NULL,
           "This must be the youngest gen, and not the only gen");
  }
  //判斷下一個(gè)內(nèi)存管理器是否能夠安全晉升
  return _next_gen->promotion_attempt_is_safe(used());
}

在上面的代碼中判斷TO空間是否為空,老年代是否存在,判斷老年代是否能夠安全晉升,繼續(xù)往下深入,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepGeneration.cpp

bool ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  //最大可用空間
  size_t available = max_available();
  //之前晉升對(duì)象的平均大小
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  //允許的最大空間 > 平均晉升大小或者是允許最大空間 > 最大晉升大小
  bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
  if (Verbose && PrintGCDetails) {
    gclog_or_tty->print_cr(
      "CMS: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
      "max_promo("SIZE_FORMAT")",
      res? "":" not", available, res? ">=":"<",
      av_promo, max_promotion_in_bytes);
  }
  return res;
}

從上面的代碼中可以看出如果:允許的最大空間 > 平均晉升大小或者允許最大空間 > 最大晉升大小,那么晉升成功。

年輕代晉升失敗可能導(dǎo)致不斷進(jìn)行CMS GC。

第五部分
// 主要判斷方法去是否需要繼續(xù)GC
if (CMSClassUnloadingEnabled && _permGen->should_concurrent_collect()) {
    bool res = update_should_unload_classes();
    if (res) {
      if (Verbose && PrintGCDetails) {
        gclog_or_tty->print_cr("CMS perm gen initiated");
      }
      return true;
    }
  }

在上面的代碼中判斷是否開(kāi)啟了 CMSClassUnloadingEnabled 參數(shù),然后調(diào)用 should_concurrent_collect 方法,(永久代的判斷和老年的方法是通用的)判斷是否需要進(jìn)行GC。

總結(jié)

通過(guò)上面的分析,我們總結(jié)了一下情況可能會(huì)觸發(fā) CMS GC

  1. 請(qǐng)求進(jìn)行一次fullgc,如調(diào)用System.gc時(shí)

  2. 當(dāng)沒(méi)有設(shè)置UseCMSInitiatingOccupancyOnly時(shí),會(huì)動(dòng)態(tài)計(jì)算。如果完成CMS回收的所需要的預(yù)計(jì)的時(shí)間小于預(yù)計(jì)的CMS回收的分代填滿的時(shí)間,就進(jìn)行回收

  3. 調(diào)用should_concurrent_collect()方法返回true

  4. 如果預(yù)計(jì)增量式回收會(huì)失敗時(shí),也會(huì)觸發(fā)一次回收。

  5. 如果metaSpace認(rèn)為需要回收metaSpace區(qū)域,也會(huì)觸發(fā)一次cms回收

我們對(duì)(3)的情況在進(jìn)行一下總結(jié):

  1. 老年代使用率 > 配置的initiating_occupancy,我們啟動(dòng)參數(shù)中會(huì)配置如下參數(shù):-XX:CMSInitiatingOccupancyFraction=n

  2. 在啟動(dòng)參數(shù)中配置了UseCMSInitiatingOccupancyOnly,如果空間使用率沒(méi)有達(dá)到閾值,直接返回。

  3. 堆擴(kuò)容的原因是_satisfy_allocation

  4. CompactibleFreeListSpace判斷需要進(jìn)行回收

自我介紹

我是何勇,現(xiàn)在重慶豬八戒,多學(xué)學(xué)?。?!

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫(xiě)博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,157評(píng)論 2 31
  • JVM架構(gòu) 當(dāng)一個(gè)程序啟動(dòng)之前,它的class會(huì)被類(lèi)裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,845評(píng)論 0 7
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,611評(píng)論 1 0
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(shū)(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,475評(píng)論 1 34
  • 產(chǎn)品槽點(diǎn)--那些跟我們朝夕相處的它們 記錄每天用的產(chǎn)品的一點(diǎn)問(wèn)題,其實(shí)簡(jiǎn)書(shū)真的不適合做這樣的記錄呢 還有一個(gè)姐妹篇...
    今夏Summer閱讀 517評(píng)論 0 1

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