从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址: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都是什么原因引起的。
该cause大家应该都知道,是通过代码显示调用System.gc()触发的。还有一种情况是,在visualvm等软件上通过JMX监控时有点击触发SystemGC的按钮。
JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口。正是由于 JVMTI 的强大功能,它是实现 Java 调试器,以及其它 Java 运行态测试与分析工具的基础。
可以参考这篇文章https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/
所以_jvmti_force_gc就是通过jvmti方式触发的GC
当通过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();
}
}
这个类型主要是jamp -hisot:live命令时会触发。或者若设置了PrintClassHistogramBeforeFullGC
或者PrintClassHistogramAfterFullGC时,则在fullgc之前或者之后也会触发一次GCCause=_heap_inspection的GC。
看名字就知道,它用于dump堆时,比如:
jmap -dump:format=b,file=a.hprof pid
或者设置了参数HeapDumpBeforeFullGC,HeapDumpAfterFullGC。这2个参数用于在fullgc前/后自动dump堆,便于分析fullgc前后的差异。
很见,用于whitebox测试,见https://wiki.openjdk.java.net/display/HotSpot/The+WhiteBox+testing+API
这个就是常见的内存分配失败触发的GC。比如在new 对象时。
这个用于在metaspace区域分配时分配不下,从而触发的GC
这2个就是对于设置的CMS回收器时,有一个background式的回收时的初始标记和最终标记阶段
表示触发GC的是一次cms的background式GC。可以参考源码笔记1:如何触发一次CMS回收中都有哪些原因会触发background式的GC
这个在ps中动态调整堆以及各个区大小时用到。
这个是设置的G1回收器时,若分配不下触发的GC的cause
这个和上面的区别是,这个用于分配超大对象失败时触发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;
}
这个也是用于在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做的优化