前言
最近项目中遇到定期(一个小时)发生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));
}
说明:
- 如果是采用CMS垃圾收集器,且满足如下的任何一个条件,则走并行收集的collect_mostly_concurrent流程:
- GC原因是System.gc且开启了ExplicitGCInvokesConcurrent选项;
- GC原因是GCLocker且开启了GCLockerInvokesConcurrent选项;
- 否则走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,业务停顿时间有较大的改善;