jvm源码阅读笔记[7]-从jstat -gccause命令谈到jvm中都有哪些GC cause

    从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
    个人源码地址:https://github.com/FlashLightNing/openjdk-notes
    还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
    jvm源码阅读笔记[1]:如何触发一次CMS回收
    jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
    jvm源码阅读笔记[3]:从内存分配到触发GC的细节
    jvm源码阅读笔记[4]:从GC说到vm operation
    jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
    jvm源码阅读笔记[6]:杂谈JIT中对Exception做的优化

    
    大家都知道,当我们使用以下命令时,会打印出导致GC的原因

jstat -gccause pid 1000

    

    
    可以看到最后2列分别列出了上次GC的原因和当前GC的原因。有一个”Heap Inspection Initiated GC”是因为我调用了jmap -histo:live pid。那么,今天我们就来看看,gccause显示的都会有哪些GC原因呢
    https://github.com/dmlloyd/openjdk/blob/jdk8u/jdk8u/hotspot/src/share/vm/gc_interface/gcCause.hpp#L39 中有一个Cause的枚举类,列举了都有哪些cause,用于在触发GC的时候做原因的区分。总共有27条,删掉源码中没有找到调用的,还剩17条。
    

enum Cause {
    /* public */
    _java_lang_system_gc,
    _full_gc_alot,
    _scavenge_alot,
    _allocation_profiler,//无
    _jvmti_force_gc,
    _gc_locker,
    _heap_inspection,
    _heap_dump,
    _wb_young_gc,
    _update_allocation_context_stats_inc,//无
    _update_allocation_context_stats_full,//无

    /* implementation independent, but reserved for GC use */
    _no_gc,//无
    _no_cause_specified,//无
    _allocation_failure,

    /* implementation specific */

    _tenured_generation_full,//无
    _metadata_GC_threshold,

    _cms_generation_full,//无
    _cms_initial_mark,
    _cms_final_remark,
    _cms_concurrent_mark,//background式

    _old_generation_expanded_on_last_scavenge,//无
    _old_generation_too_full_to_scavenge,//无
    _adaptive_size_policy,

    _g1_inc_collection_pause,
    _g1_humongous_allocation,

    _last_ditch_collection,
    _last_gc_cause//无
  };

    
    在调用各种GC时,需要传入具体的参数GCCause,来表示这是一次由什么原因触发的GC。上面代码后面的注释“无”表示没有在具体的调用GC的代码中找到有传入这样的一个cause,可能在jdk8中已经去掉了。
    下面来具体写一下每个有调用的cause都是什么原因引起的。

_java_lang_system_gc

    该cause大家应该都知道,是通过代码显示调用System.gc()触发的。还有一种情况是,在visualvm等软件上通过JMX监控时有点击触发SystemGC的按钮。

_jvmti_force_gc

    JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口。正是由于 JVMTI 的强大功能,它是实现 Java 调试器,以及其它 Java 运行态测试与分析工具的基础。
    可以参考这篇文章https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/
所以_jvmti_force_gc就是通过jvmti方式触发的GC

_gc_locker

    当通过jni方式操作数组或者是字符串的时候,为了避免GC过程移动数组或字符串的内存地址,jvm实现了一个GC_locker这样的东西,用于表示有线程在jni临界区内,阻止其他线程进行GC操作。当最后一个位于jni临界区内的线程退出临界区时,发起一次CGCause为_gc_locker的GC.

//退出临界区后释放该锁,并发起一次由gclocker触发的gc
void GC_locker::jni_unlock(JavaThread* thread) {
  assert(thread->in_last_critical(), "should be exiting critical region");
  MutexLocker mu(JNICritical_lock);
  _jni_lock_count--;
  decrement_debug_jni_lock_count();
  thread->exit_critical();
  /*
  减少计数器之后如果=0,则表示这个是最后一个退出jni的线程,
  则需要触发一次有gclocker的GC
  */
  if (needs_gc() && !is_active_internal()) {

    _doing_gc = true;
    {
      // Must give up the lock while at a safepoint
      MutexUnlocker munlock(JNICritical_lock);
      if (PrintJNIGCStalls && PrintGCDetails) {
        ResourceMark rm; // JavaThread::name() allocates to convert to UTF8
        gclog_or_tty->print_cr("%.3f: Thread \"%s\" is performing GC after exiting critical section, %d locked",
            gclog_or_tty->time_stamp().seconds(), Thread::current()->name(), _jni_lock_count);
      }
      Universe::heap()->collect(GCCause::_gc_locker);
    }
    _doing_gc = false;
    _needs_gc = false;
    JNICritical_lock->notify_all();
  }
}

_heap_inspection

    这个类型主要是jamp -hisot:live命令时会触发。或者若设置了PrintClassHistogramBeforeFullGC
或者PrintClassHistogramAfterFullGC时,则在fullgc之前或者之后也会触发一次GCCause=_heap_inspection的GC。

_heap_dump

    看名字就知道,它用于dump堆时,比如:

jmap -dump:format=b,file=a.hprof pid

    或者设置了参数HeapDumpBeforeFullGC,HeapDumpAfterFullGC。这2个参数用于在fullgc前/后自动dump堆,便于分析fullgc前后的差异。

_wb_young_gc

    很见,用于whitebox测试,见https://wiki.openjdk.java.net/display/HotSpot/The+WhiteBox+testing+API

_allocation_failure

    这个就是常见的内存分配失败触发的GC。比如在new 对象时。

_metadata_GC_threshold

    这个用于在metaspace区域分配时分配不下,从而触发的GC

_cms_initial_mark,_cms_final_remark

    这2个就是对于设置的CMS回收器时,有一个background式的回收时的初始标记和最终标记阶段

_cms_concurrent_mark

    表示触发GC的是一次cms的background式GC。可以参考源码笔记1:如何触发一次CMS回收中都有哪些原因会触发background式的GC

_adaptive_size_policy

    这个在ps中动态调整堆以及各个区大小时用到。

_g1_inc_collection_pause

    这个是设置的G1回收器时,若分配不下触发的GC的cause

_g1_humongous_allocation

    这个和上面的区别是,这个用于分配超大对象失败时触发GC。
    对于分配普通大小的对象和超大对象,是调用的不同的方法,所以也有不同的GCCause的区分。

if (!isHumongous(word_size)) {
      result = attempt_allocation(word_size, &gc_count_before, &gclocker_retry_count);
    } else {
      result = attempt_allocation_humongous(word_size, &gc_count_before, &gclocker_retry_count);
    }
    if (result != NULL) {
      return result;
    }

    

_last_ditch_collection

    这个也是用于在metaspace区域分配不下时,最后的一次回收。若GCCause=_metadata_GC_threshold的GC后,仍分配不下,则会最后触发一次cause=_last_ditch_collection的回收。此次回收会清除软引用。若GC完再分配不下,就OOM了。这也正符合了软引用的定义:在OOM发生之前会进行回收。
    相关源码:

if (!MetadataAllocationFailALot) {
    _result = _loader_data->metaspace_non_null()->allocate(_size, _mdtype);
    if (_result != NULL) {
      return;
    }
  }

  if (initiate_concurrent_GC()) {
    // For CMS and G1 expand since the collection is going to be concurrent.
    _result = _loader_data->metaspace_non_null()->expand_and_allocate(_size, _mdtype);
    if (_result != NULL) {
      return;
    }

    log_metaspace_alloc_failure_for_concurrent_GC();
  }

  /
  先不清除软引用
  */
  heap->collect_as_vm_thread(GCCause::_metadata_GC_threshold);

  _result = _loader_data->metaspace_non_null()->allocate(_size, _mdtype);
  if (_result != NULL) {
    return;
  }


  _result = _loader_data->metaspace_non_null()->expand_and_allocate(_size, _mdtype);
  if (_result != NULL) {
    return;
  }

  /* 
   如果扩容失败,做最后一次回收,且会回收软引用。
  */
   heap->collect_as_vm_thread(GCCause::_last_ditch_collection);
  _result = _loader_data->metaspace_non_null()->allocate(_size, _mdtype);
  if (_result != NULL) {
    return;
  }

  if (Verbose && PrintGCDetails) {
    gclog_or_tty->print_cr("\nAfter Metaspace GC failed to allocate size "
                           SIZE_FORMAT, _size);
  }

  if (GC_locker::is_active_and_needs_gc()) {
    set_gc_locked();
  }

    能找到的有触发的也就这些了。知道这些各种原因,对于使用jstat -gccause 排查FGC问题时也是帮助很大。
    

    从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
    个人源码地址:https://github.com/FlashLightNing/openjdk-notes
    还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
    jvm源码阅读笔记[1]:如何触发一次CMS回收
    jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
    jvm源码阅读笔记[3]:从内存分配到触发GC的细节
    jvm源码阅读笔记[4]:从GC说到vm operation
    jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
    jvm源码阅读笔记[6]:杂谈JIT中对Exception做的优化

你可能感兴趣的:(jvm,垃圾回收)