EZLippi-浮生志

为什么不推荐使用-XX:+DisableExplicitGC

System.gc()与NIO框架

System.gc()默认会触发一次Full Gc,如果在代码中不小心调用了System.gc()会导致JVM间歇性的暂停,但有些NIO框架
比如Netty框架经常会使用DirectByteBuffer来分配堆外内存,在分配之前会显式的调用System.gc(),如果开启了DisableExplicitGC
这个参数,会导致System.gc()调用变成一个空调用,没有任何作用,反而会导致Netty框架无法申请到足够的堆外内存,从而产生
java.lang.OutOfMemoryError: Direct buffer memory.

既然是堆外内存,为什么触发Full GC会有助于回收堆外内存呢,Full GC不是只回收JVM的堆内存吗?这就要了解下DirectByteBuffer的
回收机制了,DirectByteBuffer没有finalizer,它的native memory的清理工作是通过sun.misc.Cleaner自动完成的,而sum.misc.Cleaner
是一种基于虚引用的回收工具,从JDK源码也可以看到:

1
public class Cleaner extends PhantomReference<Object>

当GC检查到Cleaner的引用变成虚引用可达时,reference-handler线程会调用Cleaner的clean方法回收内存,这个机制可以在
java.lang.ref.Reference$ReferenceHandler里看到,Reference类加载的时候会创建reference-handler线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void run() {
for (;;) {
//省略了一些
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/

handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
}

JVM在做Full GC时会对引用作处理(reference processing),当GC检测到Cleaner的引用变成虚可达时,
引用Handler线程会触发Cleaner对DirectByteBuffer对象作清理工作.

ExplicitGCInvokesConcurrent

既然不推荐使用DisableExplicitGC这个参数,那有没有什么办法能尽量减少显式调用System.gc()带来的GC停顿呢,JVM提供了
ExplicitGCInvokesConcurrent和ExplicitGCInvokesConcurrentAndUnloadsClasses这两个参数来保证显式调用System.gc()
触发的是一个并发GC周期而不是Full GC,这两个参数只能配合CMS使用(-XX:+UseConcMarkSweepGC):

CMS GC周期内也会做reference-processing,因此也能够触发对DirectByteBuffer内存的回收,减少了Full GC带来的长时间
停顿.

后记

当没有开启DisableExplicitGC这个参数时,你会发现JVM每个小时会执行一次Full GC,这是因为JVM在做分布式GC,为RMI服务的,
可以通过sun.rmi.dgc.server.gcInterval这个参数来修改GC间隔,默认是一个小时,具体的参数可以参考http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html

🐶 您的支持将鼓励我继续创作 🐶