sun.misc.CleanerまたはPhotomReferenceを使って、ヒープ外メモリの自動放出を実現します。


以前のブログです。System.gc()-XX:+Dispable ExplicitGC起動パラメータとDirectByteBufferのメモリをリリースします。 記事の終わりに:java NIOパッケージといえば、sun.misc.CleanerとPhantomReferenceを通じてヒープ外メモリの自動放出を実現します。CleeanerとPhotomReferenceの使用を学び、自分でカプセル化してヒープ外メモリの自動放出を実現します。
sun.misc.CleanerはJDK内部で提供された非ヒープメモリ資源を解放するAPIである。JVMはメモリ資源を自動的にリリースしてくれるだけです。しかし、このようなシステムを通じて、他の資源を簡単に解放できるように調整機構を提供しています。まずCleeanerの使い方を見ます。
package direct;
public class FreeMemoryTask implements Runnable
{
	private long address = 0;

	public FreeMemoryTask(long address)
	{
		this.address = address;
	}

	@Override
	public void run()
	{
		System.out.println("runing FreeMemoryTask");

		if (address == 0)
		{
			System.out.println("already released");
		} else
		{
			GetUsafeInstance.getUnsafeInstance().freeMemory(address);
		}

	}
}
これはRunnableインターフェースの種類を実現しました。機能はヒープ外メモリを解放することです。これは私たちがしなければならないことです。JVMは私たちを手伝ってくれません。
public class ObjectInHeapUseCleaner
{
	private long address = 0;

	public ObjectInHeapUseCleaner()
	{
		address = GetUsafeInstance.getUnsafeInstance().allocateMemory(
				2 * 1024 * 1024);
	}

	public static void main(String[] args)
	{
		while (true)
		{
			System.gc();

			ObjectInHeapUseCleaner heap = new ObjectInHeapUseCleaner();

			//   heap    ,       FreeMemoryTask
			Cleaner.create(heap, new FreeMemoryTask(heap.address));
		}
	}
}
このコードを実行すると、プログラムは正常に動作し、OOMは発生しません。
 Clearer.create()は2つのパラメータが必要です。最初のパラメータ:監視が必要なメモリオブジェクト、2番目のパラメータ:プログラムリリースリソースのフィードバック。JVMがGCを行う時、私達が監視する対象が存在しないことを発見したら(Cleeanerの対象だけに引用されています。これは幽霊引用です。)、第二のパラメータRunnable.run()方法の論理を呼び出して、Runnable.run(この時はすでにヒープ外メモリを解放しました)を実行したら、JVMは自動的にヒープメモリの中の監視対象を釈放します。sun.misc.Cleanerを使うのは簡単です。
次に私達はsun.misc.Cleanerを使わない場合、どうやって資源を放出しますか?
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;

public class MyOwnCleaner
{

	private static ReferenceQueue refQueue = new ReferenceQueue();

	private static Map, Runnable> taskMap = new HashMap, Runnable>();

	static
	{
		new CleanerThread().start();
	}

	public static void clear(Object heapObject, Runnable task)
	{
		//  heapObject        ,reference    JVM        
		//         reference      
		PhantomReference reference = new PhantomReference(
				heapObject, refQueue);

		taskMap.put(reference, task);

	}

	//     
	private static class CleanerThread extends Thread
	{
		@Override
		public void run()
		{
			while (true)
			{
				try
				{
					@SuppressWarnings("unchecked")
					Reference refer = (Reference) refQueue
							.remove();

					Runnable r = taskMap.remove(refer);
					r.run();
				} catch (InterruptedException e)
				{

				}

			}
		}
	}

}
ここではPhotomReferenceとReferenceQueを使っていますが、これはJVM内部の対象破壊メカニズムです。山の中の対象が存在しない場合は、強い引用があり、幽霊引用のみが存在する場合、JVMは自動的にこの対象の幽霊を関連する引用列に引用する。
private static ReferenceQueue refQueue = new ReferenceQueue();
これは列を引用して、JVMは自動的にPhotomReferenceを引用して列の中に入ります。つまり、この列をポーリングすれば、どのオブジェクトがJVMによって回収されるかが分かります。
public static void clear(Object heapObject, Runnable task)
{
	//  heapObject        ,reference    JVM        
	//         reference      
	PhantomReference reference = new PhantomReference(
			heapObject, refQueue);
	taskMap.put(reference, task);
}
このコードは、かなりのもので、私たちはヒープの中のオブジェクトに監視カメラを追加しました。taskMapは幽霊引用と対応するコード回収ロジックを記録しています。
その後、私たちはバックグランドでCleeaner Threadスレッドを開いて、絶えずポーリング引用キューを発見したら、キューの中にデータがあります。
対応するRunnableを探し出して、そのrun方法を呼び出して、ヒープの対象のheappObjectの中で引用するヒープ外メモリを釈放します。テストコードは以下の通りです。
public class Test
{
	private long address = 0;

	public Test()
	{
		address = GetUsafeInstance.getUnsafeInstance().allocateMemory(
				2 * 1024 * 1024);
	}

	public static void main(String[] args)
	{
		while (true)
		{
			Test heap = new Test();

			MyOwnCleaner.clear(heap, new FreeMemoryTask(heap.address));

			System.gc();
		}
	}
}
テストコードを実行しても、OOMは報告されません。つまり、スタック外メモリの自動放出が正しく実現されました。