Java直接內存分配和釋放方式

一. 正常分配,回收由GC負責

添加jvm啓動參數:-verbose:gc -XX:+PrintGCDetails -XX:MaxDirectMemorySize=40M 循環執行以下代碼,可以看到頻繁fullGC.

ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

當然我也找到一種不需要GC回收由程序員自己回收的方式,不推薦使用

((DirectBuffer)buffer).cleaner().clean();

二. 偏方分配,不安全回收內存由程序員自己負責

如果循環執行下面分配內存代碼而不釋放會OutOfMemory
由於Unsafe是不對外開放的所有使用反射獲取theUnsafe屬性,第三行f.get(null)能夠正確執行的原因是 theUnsafe屬性是靜態屬性。

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe)f.get(null);
long pointer = unsafe.allocateMemory(1024 * 1024 * 20);

//釋放內存
unsafe.freeMemory(pointer);

原理分析

查看ByteBuffer 源碼可知 ByteBuffer.allocateDirect()創建DirectByteBuffer實例,DirectByteBuffer通過Unsafe分配內存,下面具體看一下執行過程。
1. 調用 ByteBuffer.allocateDirect(int cap)
2. 創建DirectByteBuffer:主要分三步,第一步調用Bits.reserveMemory(long size, int cap)) 在函數內部調用System.gc() 通知GC如有必要進行垃圾回收,第一次調用一般不會觸發;第二步,調用Unsafe.allocateMemory(long var )方法分配內存;第三步,調用Cleaner.create(Object var0, Runnable var1) 創建Cleaner對象,用於回收內存。
3. Cleaner類繼承自PhantomReference< Object>在此處保留Cleaner對象的虛引用。此類中還包含一個靜態DirectByteBuffer引用隊列用於得知那些虛引用所指向的對象已回收,這是一個很棒的設計因爲jvm不知道堆外內存的使用情況,通過DirectByteBuffer對象的回收來間接控制堆外內存的回收。
4. 在 2 中System.gc() 給GC一個調用建議,如果在接下來的堆外內存分配中發現空間不足就會觸發fullGC 。可以通過XX:MaxDirectMemorySize=40M來模擬。GC之後,“觸發”調用Cleaner.clean() 方法,進而調用Deallocator.run() 在run方法中調用unsafe.freeMemory(long var1)釋放堆外內存。
5. 爲驗證是否因爲System.gc() 可在jvm啓動參數加入-XX:+DisableExplicitGC禁用該代碼。

建議使用新標籤打開
6. “觸發”階段,事實上是在Reference類中創建了一個叫Reference Handler的高優先級的守護線程監控着這些“引用”指向的對象。該線程執行Reference類的tryHandlePending方法,判斷如對象是Cleaner額外調用clean方法釋放內存。
c = r instanceof Cleaner ? (Cleaner) r : null;
................
if (c != null) {
c.clean();
return true;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章