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。

相关文章
相关标签/搜索