System.gc源码分析

前言

最近项目中遇到定期(一个小时)发生Full gc的情况,经过排查发现是sun.misc.GC的Daemon线程会定时调用Sysgem.gc,根源是项目中用到了JMX导致的;
最终的方案选择的是启用ExplicitGCInvokesConcurrent和合理调大sun.rmi.dgc.client.gcInterval参数;

那么为什么启用ExplicitGCInvokesConcurrent会减缓Full GC的停顿时间呢?下面的文章将进行探讨。

System.gc简介

在Java层面,System.gc调用的是Runtime.getRuntime().gc():


JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
    JVM_GC();
}

在JVM层面,Runtime.getRuntime().gc()调用的是JVM_GC(见jvm.cpp):

JVM_ENTRY_NO_ENV(void, JVM_GC(void))
  JVMWrapper("JVM_GC");
  if (!DisableExplicitGC) {
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
  }
JVM_END

可见,如果打开了DisableExplicitGC开关,System.gc实际上什么都不做;

collect方法分析

Universe::heap()->collect的具体实现在GenCollectedHeap::collect方法:

void GenCollectedHeap::collect(GCCause::Cause cause) {
  if (should_do_concurrent_full_gc(cause)) {
#if INCLUDE_ALL_GCS
    // mostly concurrent full collection
    collect_mostly_concurrent(cause);
#else  // INCLUDE_ALL_GCS
    ShouldNotReachHere();
#endif // INCLUDE_ALL_GCS
  } else {
#ifdef ASSERT
    if (cause == GCCause::_scavenge_alot) {
      // minor collection only
      collect(cause, 0);
    } else {
      // Stop-the-world full collection
      collect(cause, n_gens() - 1);
    }
#else
    // Stop-the-world full collection
    collect(cause, n_gens() - 1);
#endif
  }
}
bool GenCollectedHeap::should_do_concurrent_full_gc(GCCause::Cause cause) {
  return UseConcMarkSweepGC &&
         ((cause == GCCause::_gc_locker && GCLockerInvokesConcurrent) ||
          (cause == GCCause::_java_lang_system_gc && ExplicitGCInvokesConcurrent));
}

说明:

  1. 如果是采用CMS垃圾收集器,且满足如下的任何一个条件,则走并行收集的collect_mostly_concurrent流程:
  • GC原因是System.gc且开启了ExplicitGCInvokesConcurrent选项;
  • GC原因是GCLocker且开启了GCLockerInvokesConcurrent选项;
  1. 否则走stop the world的collect收集流程;

collect_mostly_concurrent

void GenCollectedHeap::collect_mostly_concurrent(GCCause::Cause cause) {
  assert(!Heap_lock->owned_by_self(), "Should not own Heap_lock");

  MutexLocker ml(Heap_lock);
  // Read the GC counts while holding the Heap_lock
  unsigned int full_gc_count_before = total_full_collections();
  unsigned int gc_count_before      = total_collections();
  {
    MutexUnlocker mu(Heap_lock);
    VM_GenCollectFullConcurrent op(gc_count_before, full_gc_count_before, cause);
    VMThread::execute(&op);
  }
}

collect_mostly_concurrent需要先获取堆锁,然后通过VMThread执行VM_GenCollectFullConcurrent;

collect


void GenCollectedHeap::collect_locked(GCCause::Cause cause, int max_level) {
  // Read the GC count while holding the Heap_lock
  unsigned int gc_count_before      = total_collections();
  unsigned int full_gc_count_before = total_full_collections();
  {
    MutexUnlocker mu(Heap_lock);  // give up heap lock, execute gets it back
    VM_GenCollectFull op(gc_count_before, full_gc_count_before,
                         cause, max_level);
    VMThread::execute(&op);
  }
}

collect最终调用的是collect_locked方法,通过VMThread执行VM_GenCollectFull

总结:对于System.gc,开启ExplicitGCInvokesConcurrent之后走的是VMThread的VM_GenCollectFullConcurrent,否则走的是VMThread的VM_GenCollectFull;由于VMThread会stop the world,因此这两种分支都会暂停业务线程

VM_GenCollectFull

class VM_GenCollectFull: public VM_GC_Operation {
 private:
  int _max_level;
 public:
  VM_GenCollectFull(unsigned int gc_count_before,
                    unsigned int full_gc_count_before,
                    GCCause::Cause gc_cause,
                      int max_level)
    : VM_GC_Operation(gc_count_before, gc_cause, full_gc_count_before, true /* full */),
      _max_level(max_level) { }
  ~VM_GenCollectFull() {}
  virtual VMOp_Type type() const { return VMOp_GenCollectFull; }
  virtual void doit();
};

VM_GenCollectFull的执行逻辑是放在doit方法:

void VM_GenCollectFull::doit() {
  SvcGCMarker sgcm(SvcGCMarker::FULL);

  GenCollectedHeap* gch = GenCollectedHeap::heap();
  GCCauseSetter gccs(gch, _gc_cause);
  gch->do_full_collection(gch->must_clear_all_soft_refs(), _max_level);
}

直接调用GenCollectedHeap::do_full_collection方法实现的;

VM_GenCollectFullConcurrent

VM_GenCollectFullConcurrent的doit方法实现如下:

void VM_GenCollectFullConcurrent::doit() {
  assert(Thread::current()->is_VM_thread(), "Should be VM thread");
  assert(GCLockerInvokesConcurrent || ExplicitGCInvokesConcurrent, "Unexpected");

  GenCollectedHeap* gch = GenCollectedHeap::heap();
  if (_gc_count_before == gch->total_collections()) {
   //虽然是full gc,但只收集yong区
    GCCauseSetter gccs(gch, _gc_cause);
    gch->do_full_collection(gch->must_clear_all_soft_refs(),0 );
  } 
 
  MutexLockerEx x(FullGCCount_lock, Mutex::_no_safepoint_check_flag);
  assert(_full_gc_count_before <= gch->total_full_collections(), "Error");
  if (gch->total_full_collections() == _full_gc_count_before) {
 
    CMSCollector::disable_icms();
    _disabled_icms = true;
    CMSCollector::start_icms();
    // 通过CMS线程执行CMS Full GC
    CMSCollector::request_full_gc(_full_gc_count_before, _gc_cause);
  } else {
    assert(_full_gc_count_before < gch->total_full_collections(), "Error");
    FullGCCount_lock->notify_all();  // Inform the Java thread its work is done
  }
}

void CMSCollector::request_full_gc(unsigned int full_gc_count, GCCause::Cause cause) {
  GenCollectedHeap* gch = GenCollectedHeap::heap();
  unsigned int gc_count = gch->total_full_collections();
  if (gc_count == full_gc_count) {
    MutexLockerEx y(CGC_lock, Mutex::_no_safepoint_check_flag);
    //设置_full_gc_requested标志位为true,CMS线程默认2秒钟检查一次_full_gc_requested标志位,如果发现为true,开始执行CMS垃圾收集
    _full_gc_requested = true;
    _full_gc_cause = cause;
    CGC_lock->notify();   // nudge CMS thread
  } else {
    assert(gc_count > full_gc_count, "Error: causal loop");
  }
}

可以看到要么通过VMThread执行Full GC,但只收集yong区域;要么通过CMSThread执行full gc(CMSCollector::collect_in_background),只有初始标记和再次标记阶段stop the world;相对于通过VMThread执行的full gc,业务停顿时间有较大的改善;