在nio以前,是沒有光明正大的做法的,有一個work around的辦法是直接訪問Unsafe類。如果你使用Eclipse,默認是不允許訪問sun.misc下面的類的,你需要稍微修改一下,給Type Access Rules里面添加一條所有類都可以訪問的規則:
在使用Unsafe類的時候:
Unsafe f = Unsafe.getUnsafe();
發現還是被拒絕了,拋出異常:
java.lang.SecurityException: Unsafe
正如Unsafe的類注釋中寫道:
Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.
於是,只能使用反射來做這件事;
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe us = (Unsafe) f.get(null);
long id = us.allocateMemory(1024 * 1024 * 1024);
其中,allocateMemory返回一個指針,並且其中的數據是未初始化的。如果要釋放這部分內存的話,需要調用freeMemory或者reallocateMemory方法。Unsafe對象提供了一系列put/get方法,例如putByte,但是只能一個一個byte地put,我不知道這樣會不會影響效率,為什么不提供一個putByteArray的方法呢?
示例:
import sun.misc.Unsafe;
public class ObjectInHeap
{
private long address = 0;
private Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();
public ObjectInHeap()
{
address = unsafe.allocateMemory(2 * 1024 * 1024);
}
// Exception in thread "main" java.lang.OutOfMemoryError
public static void main(String[] args)
{
while (true)
{
ObjectInHeap heap = new ObjectInHeap();
System.out.println("memory address=" + heap.address);
}
}
}
這段代碼會拋出OutOfMemoryError。這是因為ObjectInHeap對象是在堆內存中分配的,當該對象被垃圾回收的時候,並不會釋放堆外內存,因為使用Unsafe獲取的堆外內存,必須由程序顯示的釋放,JVM不會幫助我們做這件事情。由此可見,使用Unsafe是有風險的,很容易導致內存泄露。
4、正確釋放Unsafe分配的堆外內存
雖然第3種情況的ObjectInHeap存在內存泄露,但是這個類的設計是合理的,它很好的封裝了直接內存,這個類的調用者感受不到直接內存的存在。那怎么解決ObjectInHeap中的內存泄露問題呢?可以覆寫Object.finalize(),當堆中的對象即將被垃圾回收器釋放的時候,會調用該對象的finalize。由於JVM只會幫助我們管理內存資源,不會幫助我們管理數據庫連接,文件句柄等資源,所以我們需要在finalize自己釋放資源。
import sun.misc.Unsafe;
public class RevisedObjectInHeap
{
private long address = 0;
private Unsafe unsafe = GetUsafeInstance.getUnsafeInstance();
// 讓對象占用堆內存,觸發[Full GC
private byte[] bytes = null;
public RevisedObjectInHeap()
{
address = unsafe.allocateMemory(2 * 1024 * 1024);
bytes = new byte[1024 * 1024];
}
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("finalize." + bytes.length);
unsafe.freeMemory(address);
}
public static void main(String[] args)
{
while (true)
{
RevisedObjectInHeap heap = new RevisedObjectInHeap();
System.out.println("memory address=" + heap.address);
}
}
}
我們覆蓋了finalize方法,手動釋放分配的堆外內存。如果堆中的對象被回收,那么相應的也會釋放占用的堆外內存。這里有一點需要注意下:
// 讓對象占用堆內存,觸發[Full GC private byte[] bytes = null;
這行代碼主要目的是為了觸發堆內存的垃圾回收行為,順帶執行對象的finalize釋放堆外內存。如果沒有這行代碼或者是分配的字節數組比較小,程序運行一段時間后還是會報OutOfMemoryError。這是因為每當創建1個RevisedObjectInHeap對象的時候,占用的堆內存很小(就幾十個字節左右),但是卻需要占用2M的堆外內存。這樣堆內存還很充足(這種情況下不會執行堆內存的垃圾回收),但是堆外內存已經不足,所以就不會報OutOfMemoryError。
