总结:

如果jvm参数中设置了-XX:+DisableExplicitGC,那么代码中手动调用System.gc()就不会生效。而有些框架中因为是使用的堆外内存,必须手动调用System.gc()来释放。如果禁用掉就会导致堆外内存使用一直增长,造成内存泄露。

 

详解:

直接内存与System.gc()

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源码也可以看到:         

public class Cleaner extends PhantomReference<Object>

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

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对象作清理工作.

CMS垃圾回收器下的推荐配置

既然不推荐使用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间隔,默认是一个小时。                  

System.gc常识

  • system.gc其实是做一次full gc
  • system.gc会暂停整个进程
  • system.gc一般情况下我们要禁掉,使用-XX:+DisableExplicitGC
  • system.gc在cms gc下我们通过-XX:+ExplicitGCInvokesConcurrent来做一次稍微高效点的GC(效果比Full GC要好些)
  • system.gc最常见的场景是RMI/NIO下的堆外内存分配等

参考:http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/sunrmiproperties.html   

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐