System.gc()功能
说到System.gc()相信大家并不陌生,如果你调用了这个方法,那么JVM会尝试执行一次gc()。
多数情况下,我们并不需要使用这个方法,因为JVM的功能已经足够强大,它知道在什么时候触发gc是最合适的。但是有些场合下System.gc()还是有用的,比如下面的这个场景,
假设你的线上服务经常会在中午触发一次老年代gc, 而中午恰恰是你的业务高峰期,这种情况下会导致午高峰时期出现部分接口因为gc而超时,带来不好的影响。经过排查你发现在午高峰之前,老年代的容量就比较高了,经过午高峰恰恰达到了触发老年代gc的条件。
在上面的场景下你会怎么做?有的人会说做扩容,将服务的压力分散在更多的机器上。这么做的确能很大程度上缓解问题,但是无疑增大了研发的投入。还有没有别的方法呢?这时候System.gc()就派上用场了,我们可以在午高峰之前通过定时任务触发一次System.gc(),将老年代进行回收,那么午高峰就能较安全的度过了。
上面了说了这么多System.gc()的功能和场景,下面我们看一下System.gc()是怎么触发gc的,以及和它相关的一些JVM参数。
代码分析
public static void gc() {
Runtime.getRuntime().gc();
}
System.gc()只是一个代理方法,实际上调用的是Runtime的gc()方法,Runtime.gc()是一个native方法,其实现在JVM中。下面我们看一下其实现,
JNIEXPORT void JNICALL
Java_java_lang_Runtime_gc(JNIEnv *env, jobject this)
{
JVM_GC();
}
JVM_ENTRY_NO_ENV(void, JVM_GC(void))
JVMWrapper("JVM_GC");
if (!DisableExplicitGC) {
Universe::heap()->collect(GCCause::_java_lang_system_gc);
}
JVM_END
根据上面的代码可以看出,最终的清理过程是由Universe::heap()->collect()完成的,并且触发清理的理由是开发者显示的调用了System.gc()方法。由于Universe::heap()->collect()是通用的垃圾回收流程,这里不适合展开,所以我们只看一下针对_java_lang_system_gc有哪些特殊处理。
并行full gc
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 gc并且开启了ExplicitGCInvokesConcurrent的时候,如果调用System.gc(),就会执行并行的full gc。
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);
}
}
那么什么是并行的full gc呢?这里有一篇文章http://lovestblog.cn/blog/2015/05/07/system-gc/很好的解释了这个问题。