欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

System.gc源码分析

程序员文章站 2022-04-18 19:24:12
...

前言

最近项目中遇到定期(一个小时)发生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,业务停顿时间有较大的改善;