如何在 Java 中垃圾回收直接缓冲区

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1854398/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 23:18:17  来源:igfitidea点击:

How to garbage collect a direct buffer in Java

javamemory-leaksbufferbytebuffer

提问by mglmnc

I have a memory leak that I have isolated to incorrectly disposed direct byte buffers.

我有一个内存泄漏,我已将其隔离为错误处置的直接字节缓冲区。

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

The GC collects the objects that harbor these buffers but does not dispose of the buffer itself. If I instantiate enough of the transient objects containing buffers, I get this encouraging message:

GC 收集包含这些缓冲区的对象,但不会处理缓冲区本身。如果我实例化足够多的包含缓冲区的瞬态对象,我会收到以下令人鼓舞的消息:

java.lang.OutOfMemoryError: Direct buffer memory

I have been searching up this problem and apparently

我一直在寻找这个问题,显然

buff.clear();

and

System.gc();

do not work.

不工作。

采纳答案by Stephen C

I suspect that somewhere your application has a reference to the ByteBuffer instance(s) and that is preventing it from being garbage collected.

我怀疑您的应用程序在某处引用了 ByteBuffer 实例,这阻止了它被垃圾收集。

The buffer memory for a direct ByteBuffer is allocated outside of the normal heap (so that the GC doesn't move it!!). However, the ByteBuffer API provides no method for explicitly disposing of / deallocating a buffer. So I assume that the garbage collector will do it ... once it determines that the ByteBuffer object is no longer referenced.

直接 ByteBuffer 的缓冲内存分配在普通堆之外(这样 GC 不会移动它!!)。但是,ByteBuffer API 没有提供显式处置/取消分配缓冲区的方法。所以我假设垃圾收集器会这样做……一旦它确定不再引用 ByteBuffer 对象。

回答by Greg Hewgill

The ByteBufferdocumentation says:

ByteBuffer文件说:

A direct byte buffer may be created by invoking the allocateDirectfactory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

可以通过调用allocateDirect此类的工厂方法来创建直接字节缓冲区。此方法返回的缓冲区通常比非直接缓冲区具有更高的分配和解除分配成本。直接缓冲区的内容可能驻留在正常的垃圾收集堆之外,因此它们对应用程序内存占用的影响可能并不明显。因此,建议将直接缓冲区主要分配给受底层系统本地 I/O 操作影响的大型、长期存在的缓冲区。一般而言,最好仅在程序性能产生可衡量的增益时才分配直接缓冲区。

In particular, the statement "may reside outside of the normal garbage-collected heap" seems relevant to your example.

特别是,“可能驻留在普通垃圾收集堆之外”这句话似乎与您的示例相关。

回答by Andreas Dolk

The allocated memory is realized through a native libary. This memory will be freed when the ByteBuffer#finalize method is called, iaw when the Buffer is gc'd. Have a look at the allocate() and finalize() implementations of DirectByteBufferImpl.

分配的内存是通过本地库实现的。当 ByteBuffer#finalize 方法被调用时,这个内存将被释放,当 Buffer 被 gc'd 时会被释放。看看DirectByteBufferImpl的 allocate() 和 finalize() 实现。

buff.clear()is not necessary, System.gc()will only help if, like others already mentioned, there's no more reference left to the ByteBuffer object.

buff.clear()没有必要,System.gc()只有在像其他人已经提到的那样,没有更多对 ByteBuffer 对象的引用时才会有所帮助。

回答by Li Pi

The DBB will be deallocated once it hits the reference queue, and the finalizer is run. However, as we cannot depend on a finalizer to run, we can use reflection to manually call its "cleaner".

DBB 一旦到达引用队列就会被释放,并且终结器运行。然而,由于我们不能依赖终结器来运行,我们可以使用反射来手动调用它的“清洁器”。

Using reflection:

使用反射:

/**
* 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);

}

回答by Antoine CHAMBILLE

Here is a refined implementation that will work for any direct buffer:

这是一个适用于任何直接缓冲区的改进实现:

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);
        }
    }
}

回答by Brett Okken

As long as you are relying on sun (oracle) specific implementation, a better choice than trying to change the visibility of java.nio.DirectByteBuffer is to use the sun.nio.ch.DirectBuffer interface via reflections.

只要您依赖于 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);
        }
    }
}