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 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索