JVM之CMSGC觸發

概述

最近一直迷惑CMS GC觸發有那些情況,專門去研究了一下CMS Thread 的源碼。廢話不說了,讓我們開始探究之旅。

ConcurrentMarkSweepThread

在啟動JVM虛擬機時,進行各種初始化操作,其中就包括了GC線程的初始化,CMS GC線程初始化主要是通過 ConcurrentMarkSweepThread(簡稱CMSThread) 類,下面具體研究一下該類的構造函數,源碼地址: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;
  //設置線程名字
  set_name("Concurrent Mark-Sweep GC Thread");

  if (os::create_thread(this, os::cgc_thread)) {
    int native_prio;
    //UseCriticalCMSThreadPriority默認為false,如果配置為true,VMThread 可能不會獲得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 線程的初始化,主要是設置線程名稱以及線程的優先級等。
與JAVA的線程類似,在 run 方法中,定義了 CMSThread 線程的工作,接下來,我們分析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的執行
    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) {
    //如果沒有觸發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);
  }
 ...............................省略....................................

在上面的代碼中,等待各種初始化操作的完成,然后通過while循環,檢測是否有觸發GC,當觸發GC時,調用 collect_in_background 進行GC操作。關于CMS的GC詳情以后進行分析,我們本次主要分析CMS GC觸發的原因。從上面的代碼中可以看出,在 sleepBeforeNextCycle 方法中檢查是否要進行GC。

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

上面的代碼中,判斷CMSIncrementalMode是否為True,如果為true,執行 icms_wait 方法,如果為false,讓線程阻塞2000ms,當阻塞時間超時以后,調用shouldConcurrentCollect 方法檢查是否需要執行CMS GC。

CMS GC觸發的情況

在上面的小節中,我們分析到 CMSThread 調用 shouldConcurrentCollect 方法來判斷是否觸發CMS GC,下面我們分析一下 shouldConcurrentCollect 內部的具體實現,該方法篇幅過長,我們將將源碼分段講解,源碼地址:hotspot\src\share\vm\gc_implementation\concurrentMarkSweep\concurrentMarkSweepGeneration.cpp

第一部分
//有FullGC的請求
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的請求,就觸發一次GC(比如調用System.gc()觸發GC)

第二部分
//UseCMSInitiatingOccupancyOnly默認false
//UseCMSInitiatingOccupancyOnly為false,會動態計算閾值
//預計完成CMS回收所需要的時間小于預計的老年代填滿的時間,則進行回收。
if (!UseCMSInitiatingOccupancyOnly) {
   if (stats().valid()) {
     if (stats().time_until_cms_start() == 0.0) {
       return true;
     }
   } else {
     //_bootstrap_occupancy默認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;
     }
   }
 }

如果預計完成CMS回收所需要的時間小于預計的老年代填滿的時間,則進行回收。

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

在上面的代碼種可以看出調用 should_concurrent_collect 方法判斷,是否觸發GC,深入到該方法中,去了解具體的實現。

bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const {
  //內存使用率大于初始化參數
  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;
  }
  //在進行堆擴容
  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;
}

在上面的代碼中,首先判斷老年代內存使用率是否大于初始化參數,如果為true,則觸發GC,如果為false,且UseCMSInitiatingOccupancyOnly 為true,則返回false。
接下來判斷是否在進行堆擴容,如果為true,則觸發GC,如果為false,則判斷CompactibleFreeListSpace是否需要進行GC。

第四部分
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  assert(gch->collector_policy()->is_two_generation_policy(),
         "You may want to check the correctness of the following");
 //判斷年輕代存活對象晉升失敗
  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;
  }

在上面的代碼中,判斷年輕代存活的對象是否可能會失敗,如果失敗,觸發GC。查看 incremental_collection_will_fail 的具體實現,源碼地址: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());
  }

在上面的代碼中,調用 incremental_collection_failed 方法判斷之前是否晉升失敗,默認為false,同時調用年輕代的 collection_attempt_is_safe 方法,判斷本次晉升是否失敗,根據或操作的結果判斷是否進行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;
  }
  //下一個內存管理器為空
  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");
  }
  //判斷下一個內存管理器是否能夠安全晉升
  return _next_gen->promotion_attempt_is_safe(used());
}

在上面的代碼中判斷TO空間是否為空,老年代是否存在,判斷老年代是否能夠安全晉升,繼續往下深入,源碼地址: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();
  //之前晉升對象的平均大小
  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;
}

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

年輕代晉升失敗可能導致不斷進行CMS GC。

第五部分
// 主要判斷方法去是否需要繼續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;
    }
  }

在上面的代碼中判斷是否開啟了 CMSClassUnloadingEnabled 參數,然后調用 should_concurrent_collect 方法,(永久代的判斷和老年的方法是通用的)判斷是否需要進行GC。

總結

通過上面的分析,我們總結了一下情況可能會觸發 CMS GC

  1. 請求進行一次fullgc,如調用System.gc時

  2. 當沒有設置UseCMSInitiatingOccupancyOnly時,會動態計算。如果完成CMS回收的所需要的預計的時間小于預計的CMS回收的分代填滿的時間,就進行回收

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

  4. 如果預計增量式回收會失敗時,也會觸發一次回收。

  5. 如果metaSpace認為需要回收metaSpace區域,也會觸發一次cms回收

我們對(3)的情況在進行一下總結:

  1. 老年代使用率 > 配置的initiating_occupancy,我們啟動參數中會配置如下參數:-XX:CMSInitiatingOccupancyFraction=n

  2. 在啟動參數中配置了UseCMSInitiatingOccupancyOnly,如果空間使用率沒有達到閾值,直接返回。

  3. 堆擴容的原因是_satisfy_allocation

  4. CompactibleFreeListSpace判斷需要進行回收

自我介紹

我是何勇,現在重慶豬八戒,多學學!!!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容

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