web-dev-qa-db-ja.com

Javaでダイレクトバッファをガベージコレクションする方法

直接バイトバッファーを誤って破棄するために特定したメモリリークがあります。

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

GCは、これらのバッファーを持つオブジェクトを収集しますが、バッファー自体は破棄しません。バッファを含む一時的なオブジェクトを十分にインスタンス化すると、次の励みになるメッセージが表示されます。

Java.lang.OutOfMemoryError:ダイレクトバッファメモリ

私はこの問題を調査しており、どうやら

buff.clear();

そして

System.gc();

動作しない。

27
mglmnc

アプリケーションのどこかにByteBufferインスタンスへの参照があり、それがガベージコレクションの妨げになっていると思います。

ダイレクトByteBufferのバッファーメモリは、通常のヒープの外部に割り当てられます(GCが移動しないように!!)。ただし、ByteBuffer APIには、バッファを明示的に破棄/割り当て解除する方法はありません。したがって、ガベージコレクターがそれを実行すると想定します。ByteBufferオブジェクトが参照されなくなったことが確認されると、.

16
Stephen C

DBBが参照キューに到達すると、割り当てが解除され、ファイナライザが実行されます。ただし、ファイナライザーに依存して実行することはできないため、リフレクションを使用して手動で「クリーナー」を呼び出すことができます。

リフレクションを使用する:

/**
* DirectByteBuffers are garbage collected by using a phantom reference and a
* reference queue. Every once a while, the JVM checks the reference queue and
* cleans the DirectByteBuffers. However, as this doesn't happen
* immediately after discarding all references to a DirectByteBuffer, it's
* easy to OutOfMemoryError yourself using DirectByteBuffers. This function
* explicitly calls the Cleaner method of a DirectByteBuffer.
* 
* @param toBeDestroyed
*          The DirectByteBuffer that will be "cleaned". Utilizes reflection.
*          
*/
public static void destroyDirectByteBuffer(ByteBuffer toBeDestroyed)
    throws IllegalArgumentException, IllegalAccessException,
    InvocationTargetException, SecurityException, NoSuchMethodException {

  Preconditions.checkArgument(toBeDestroyed.isDirect(),
      "toBeDestroyed isn't direct!");

  Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
  cleanerMethod.setAccessible(true);
  Object cleaner = cleanerMethod.invoke(toBeDestroyed);
  Method cleanMethod = cleaner.getClass().getMethod("clean");
  cleanMethod.setAccessible(true);
  cleanMethod.invoke(cleaner);

}
16
Li Pi

ByteBuffer ドキュメントには次のように書かれています:

ダイレクトバイトバッファは、このクラスの allocateDirect ファクトリメソッドを呼び出すことで作成できます。このメソッドによって返されるバッファーは、通常、非ダイレクトバッファーよりも割り当てと割り当て解除のコストがいくらか高くなります。ダイレクトバッファの内容は、通常のガベージコレクションされたヒープの外に存在する可能性があるため、アプリケーションのメモリフットプリントへの影響は明らかではない場合があります。したがって、直接バッファーは、主に、基盤となるシステムのネイティブI/O操作の影響を受ける、長期間有効な大きなバッファーに割り当てることをお勧めします。一般に、ダイレクトバッファは、プログラムのパフォーマンスが測定可能なほど向上する場合にのみ割り当てるのが最適です。

特に、「通常のガベージコレクションされたヒープの外に存在する可能性があります」という文は、この例に関連しているようです。

12
Greg Hewgill

割り当てられたメモリは、ネイティブライブラリを通じて実現されます。このメモリは、ByteBuffer#finalizeメソッドが呼び出されると解放され、BufferがGCされると解放されます。 DirectByteBufferImpl のallocate()およびfinalize()の実装を確認してください。

buff.clear()は必要ありません。System.gc()は、すでに述べた他の方法と同様に、ByteBufferオブジェクトへの参照が残っていない場合にのみ役立ちます。

5
Andreas_D

以下は、ダイレクトバッファーで機能する洗練された実装です。

public static void destroyBuffer(Buffer buffer) {
    if(buffer.isDirect()) {
        try {
            if(!buffer.getClass().getName().equals("Java.nio.DirectByteBuffer")) {
                Field attField = buffer.getClass().getDeclaredField("att");
                attField.setAccessible(true);
                buffer = (Buffer) attField.get(buffer);
            }

            Method cleanerMethod = buffer.getClass().getMethod("cleaner");
            cleanerMethod.setAccessible(true);
            Object cleaner = cleanerMethod.invoke(buffer);
            Method cleanMethod = cleaner.getClass().getMethod("clean");
            cleanMethod.setAccessible(true);
            cleanMethod.invoke(cleaner);
        } catch(Exception e) {
            throw new QuartetRuntimeException("Could not destroy direct buffer " + buffer, e);
        }
    }
}
2

Sun(Oracle)固有の実装に依存している限り、Java.nio.DirectByteBufferの可視性を変更しようとするよりも、リフレクションを介してSun.nio.ch.DirectBufferインターフェースを使用することをお勧めします。

/**
 * Sun specific mechanisms to clean up resources associated with direct byte buffers.
 */
@SuppressWarnings("unchecked")
private static final Class<? extends ByteBuffer> Sun_DIRECT_BUFFER = (Class<? extends ByteBuffer>) lookupClassQuietly("Sun.nio.ch.DirectBuffer");

private static final Method Sun_BUFFER_CLEANER;

private static final Method Sun_CLEANER_CLEAN;

static
{
    Method bufferCleaner = null;
    Method cleanerClean = null;
    try
    {
        // operate under the assumption that if the Sun direct buffer class exists,
        // all of the Sun classes exist
        if (Sun_DIRECT_BUFFER != null)
        {
            bufferCleaner = Sun_DIRECT_BUFFER.getMethod("cleaner", (Class[]) null);
            Class<?> cleanClazz = lookupClassQuietly("Sun.misc.Cleaner");
            cleanerClean = cleanClazz.getMethod("clean", (Class[]) null);
        }
    }
    catch (Throwable t)
    {
        t.printStackTrace();
    }
    Sun_BUFFER_CLEANER = bufferCleaner;
    Sun_CLEANER_CLEAN = cleanerClean;
}

public static void releaseDirectByteBuffer(ByteBuffer buffer)
{
    if (Sun_DIRECT_BUFFER != null && Sun_DIRECT_BUFFER.isAssignableFrom(buffer.getClass()))
    {
        try
        {
            Object cleaner = Sun_BUFFER_CLEANER.invoke(buffer, (Object[]) null);
            Sun_CLEANER_CLEAN.invoke(cleaner, (Object[]) null);
        }
        catch (Throwable t)
        {
            logger.trace("Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t);
        }
    }
}
1
Brett Okken