Java堆外內存之四:直接使用Unsafe類操作堆外內存


在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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM