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

在nio之前,是沒有光明正大的作法的,有一個work around的辦法是直接訪問Unsafe類。若是你使用Eclipse,默認是不容許訪問sun.misc下面的類的,你須要稍微修改一下,給Type Access Rules裏面添加一條全部類均可以訪問的規則:java

在使用Unsafe類的時候:數據庫

Unsafe f = Unsafe.getUnsafe();

發現仍是被拒絕了,拋出異常:數組

java.lang.SecurityException: Unsafe

正如Unsafe的類註釋中寫道:ide

Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.this

因而,只能使用反射來作這件事; 設計

        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的方法呢?指針

示例:code

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是有風險的,很容易致使內存泄露。對象

 

四、正確釋放Unsafe分配的堆外內存blog

        雖然第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。

相關文章
相關標籤/搜索