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阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,928评论 2 31
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,627评论 0 7
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight阅读 1,396评论 1 0
  • 《深入理解Java虚拟机》笔记_第一遍 先取看完这本书(JVM)后必须掌握的部分。 第一部分 走近 Java 从传...
    xiaogmail阅读 5,041评论 1 34
  • 产品槽点--那些跟我们朝夕相处的它们 记录每天用的产品的一点问题,其实简书真的不适合做这样的记录呢 还有一个姐妹篇...
    今夏Summer阅读 449评论 0 1