您的位置:首页 > Web前端 > JavaScript

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

2017-09-24 17:44 471 查看
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨

个人源码地址: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 openjdk cms 源码 阅读