System.gc()和-XX:+DisableExplicitGC启动参数,以及DirectByteBuffer的内存释放

首先咱们修改下JVM的启动参数,从新运行以前博客中的代码。JVM启动参数和测试代码以下:java


-verbose:gc -XX:+PrintGCDetails -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=40M
import java.nio.ByteBuffer;

public class TestDirectByteBuffer
{
// -verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M
// 加上-XX:+DisableExplicitGC,也会报OOM(Direct buffer memory)
public static void main(String[] args) throws Exception
{
while (true)
{
ByteBuffer.allocateDirect(10 * 1024 * 1024);
}
}
}
与以前的JVM启动参数相比,增长了-XX:+DisableExplicitGC,这个参数做用是禁止代码中显示调用GC。代码如何显示调用GC呢,经过System.gc()函数调用。若是加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果,至关因而没有这行代码同样。程序员

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:632)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:97)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at direct.TestDirectByteBuffer.main(TestDirectByteBuffer.java:13)
Heap
PSYoungGen total 9536K, used 507K [0x1cf90000, 0x1da30000, 0x27a30000)
eden space 8192K, 6% used [0x1cf90000,0x1d00ef30,0x1d790000)
from space 1344K, 0% used [0x1d8e0000,0x1d8e0000,0x1da30000)
to space 1344K, 0% used [0x1d790000,0x1d790000,0x1d8e0000)
PSOldGen total 21888K, used 0K [0x07a30000, 0x08f90000, 0x1cf90000)
object space 21888K, 0% used [0x07a30000,0x07a30000,0x08f90000)
PSPermGen total 16384K, used 2292K [0x03a30000, 0x04a30000, 0x07a30000)
object space 16384K, 13% used [0x03a30000,0x03c6d380,0x04a30000)
显然堆内存(包括新生代和老年代)内存很充足,可是堆外内存溢出了。也就是说NIO直接内存的回收,须要依赖于System.gc()。若是咱们的应用中使用了java nio中的direct memory,那么使用-XX:+DisableExplicitGC必定要当心,存在潜在的内存泄露风险。数据库


咱们知道java代码没法强制JVM什么时候进行垃圾回收,也就是说垃圾回收这个动做的触发,彻底由JVM本身控制,它会挑选合适的时机回收堆内存中的无用java对象。代码中显示调用System.gc(),只是建议JVM进行垃圾回收,可是到底会不会执行垃圾回收是不肯定的,可能会进行垃圾回收,也可能不会。何时才是合适的时机呢?通常来讲是,系统比较空闲的时候(好比JVM中活动的线程不多的时候),还有就是内存不足,不得不进行垃圾回收。咱们例子中的根本矛盾在于:堆内存由JVM本身管理,堆外内存必需要由咱们本身释放;堆内存的消耗速度远远小于堆外内存的消耗,但要命的是必须先释放堆内存中的对象,才能释放堆外内存,可是咱们又不能强制JVM释放堆内存。编程

 

下面咱们看下new DirectByteBuffer的源码数组


DirectByteBuffer(int cap)
{

super(-1, 0, cap, cap, false);
Bits.reserveMemory(cap);
int ps = Bits.pageSize();
long base = 0;
try {
base = unsafe.allocateMemory(cap + ps);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(cap);
throw x;
}
unsafe.setMemory(base, cap + ps, (byte) 0);
if (base % ps != 0) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, cap));
viewedBuffer = null;
}
static void reserveMemory(long size)
{

synchronized (Bits.class) {
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
if (size <= maxMemory - reservedMemory) {
reservedMemory += size;
return;
}
}

// 显示调用垃圾回收
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
if (reservedMemory + size > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
}

}
能够看到:每次执行代码ByteBuffer.allocateDirect(10 * 1024 * 1024);的时候,都会调用一次System.gc()。目的很简单,就是但愿JVM赶忙把堆中的无用对象回收掉。虽然System.gc()只是建议JVM进行垃圾回收,不能强制。能够这里理解:如此频繁的建议JVM进行垃圾回收,就算堆内存还很充足,JVM也不能对咱们显示的GC视而不见啊。因此显示的使用System.gc(),仍是有用的。也就是说direct memory的释放,依赖于System.gc()触发JVM的垃圾回收动做,只有回收了堆内存中的DirectByteBuffer对象,才有可能回收DirectByteBuffer对象中占用的堆外内存空间。安全


回想下java中使用堆外内存,关于内存回收须要注意的事和没有解决的遗留问题 这篇博客中的第4节 正确释放Unsafe分配的堆外内存socket

咱们在RevisedObjectInHeap类中函数


// 让对象占用堆内存,触发[Full GC
private byte[] bytes = null;

public RevisedObjectInHeap()
{
address = unsafe.allocateMemory(2 * 1024 * 1024);

// 占用堆内存
bytes = new byte[1024 * 1024];
}
定义了1M的字节数组,就是为了让JVM赶忙进行垃圾回收,这样当堆内存中的垃圾对象被回收的时候,JVM就可以调用finalize()方法,就可以释放堆外内存。这跟NIO类库中,显示调用System.gc()目的是同样的。至此咱们能够得出:堆内存和非堆内存资源(文件句柄、socket句柄,堆外内存、数据库链接等)的同步释放,的确是一个很棘手的问题。虽然经过System.gc()可以避免内存泄露,可是严重影响系统的运行效率,由于垃圾回收会减慢系统的运行。最佳编程实践是:暴露出释放资源的接口,程序员使用完成后,显示释放,这样就可以避免堆内存和非堆内存资源的同步释放的难题。性能


RevisedObjectInHeap类中经过finalize()方法来释放堆外内存的,阅读源码能够发现,NIO中direct memory的释放并非经过finalize(),而是经过sun.misc.Cleaner实现的测试

cleaner = Cleaner.create(this, new Deallocator(base, cap));

为何不用finalize呢?由于finalize不安全,也很是影响性能。什么是sun.misc.Cleaner?这是个幽灵引用PhantomReference。后续博客将继续分析finalize和Cleaner等垃圾回收相关的知识,欢迎关注。--------------------- 做者:aitangyong 来源:CSDN 原文:https://blog.csdn.net/aitangyong/article/details/39403031 版权声明:本文为博主原创文章,转载请附上博文连接!

相关文章
相关标签/搜索