本文章为各位介绍一下关于DirectMemory和ExplicitGCInvokesConcurrent的使用方法及一些注意事项了,希望文章能够帮助到各位朋友。
其实是去年的一个bug,就是线上出现了一次java.lang.OutOfMemoryError: Direct buffer memory。
原因,从Direct Memory的回收机制说起:
我们的系统使用了一个基于nio的消息队列,而direct memory是放在堆外的,堆内放的时direct memory的引用。正常情况下当在young gc 的时候会把这个已死的引用回收,进而回收direct memory 区域。但是如果内存引用被移到old gen中时,又没有发生Full GC,那么direct memory就会一直累积不释放。最后导致直接内存OOM。其实这样也不会有问题,问题是我们在JVM中加入了-XX: DisableExplicitGC,很多jvm调优的建议参数。真正原因是:DirectByteBuffer分配空间过程中会显式调用System.gc(),以期通过Full GC来强迫已经无用的DirectByteBuffer对象释放掉它们关联的native memory。
在分配DirectByteBuffer时,会调用java.nio.Bits.reserveMemory(size, cap)方法:
代码如下 | 复制代码 |
static void reserveMemory(long size, int cap){ if(!memoryLimitSet && VM.isBooted()){ maxMemory = VM.maxDirectMemory(); memoryLimitSet = true; } // optimist! if(tryReserveMemory(size, cap)){ return; } final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess(); // retry while helping enqueue pending Reference objects // which includes executing pending Cleaner(s) which includes // Cleaner(s) that free direct buffer memory while(jlra.tryHandlePendingReference()){ if(tryReserveMemory(size, cap)){ return; } } // trigger VM's Reference processing System.gc(); // a retry loop with exponential back-off delays // (this gives VM some time to do it's job) boolean interrupted = false; try{ long sleepTime = 1; int sleeps = 0; while(true){ if(tryReserveMemory(size, cap)){ return; } if(sleeps >= MAX_SLEEPS){ break; } if(!jlra.tryHandlePendingReference()){ try{ Thread.sleep(sleepTime); sleepTime <<= 1; sleeps ; }catch(InterruptedException e){ interrupted = true; } } } // no luck throw new OutOfMemoryError("Direct buffer memory"); }finally{ if(interrupted){ // don't swallow interrupts Thread.currentThread().interrupt(); } } } |
在tryReserveMemory中计算剩余容量是否可以分配指定大小的直接内存,如果可以直接分配,否则执行System.gc()来回收直接内存,当System.gc()被禁用就会throw new OutOfMemoryError。
解决方式:
去掉-XX: DisableExplicitGC,但是nio框架中频繁调用System.gc会严重影响性能。其实,这个有更好的解决办法,使用-XX: ExplicitGCInvokesConcurrent 或 -XX: ExplicitGCInvokesConcurrentAndUnloadsClasses参数。和-XX: DisableExplicitGC一样,这两个参数也是用来改变System.gc()的默认行为用的;不同的 是这两个参数只能配合CMS使用(-XX: UseConcMarkSweepGC),而且System.gc()还是会触发GC的,只不过不是触发一个 完全stop-the-world的full GC,而是一次并发GC周期(注:一次并发周期其实就是在CMS下的一次gc,CMS只能用在full gc 中,所以,也是一次full gc 只不过效率比较高)。
总结:
当程序中出现:
使用了NIO或者NIO框架(Mina/Netty等)
使用了DirectByteBuffer分配字节缓冲区
使用了MappedByteBuffer做内存映射
最好使用ExplicitGCInvokesConcurrent参数来替代DisableExplicitGC。需要注意的是ExplicitGCInvokesConcurrent只能配合CMS收集器使用。