美文网首页Java
JVM之CMSGC触发

JVM之CMSGC触发

作者: heyong | 来源:发表于2017-11-28 18:35 被阅读374次

    概述

    最近一直迷惑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判断需要进行回收

    自我介绍

    我是何勇,现在重庆猪八戒,多学学!!!

    相关文章

      网友评论

        本文标题:JVM之CMSGC触发

        本文链接:https://www.haomeiwen.com/subject/uahebxtx.html