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

发布于 2024-08-13 19:54:54 字数 332 浏览 8 评论 0原文

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

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

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

java.lang.OutOfMemoryError: Direct buffer memory

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

buff.clear();

System.gc();

不起作用。

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:

java.lang.OutOfMemoryError: Direct buffer memory

I have been searching up this problem and apparently

buff.clear();

and

System.gc();

do not work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

只想待在家 2024-08-20 19:54:54

我怀疑您的应用程序在某个地方引用了 ByteBuffer 实例,这导致它无法被垃圾收集。

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

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

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.

寒尘 2024-08-20 19:54:54

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

}

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".

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

}
肤浅与狂妄 2024-08-20 19:54:54

ByteBuffer 文档说:

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

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

The ByteBuffer documentation says:

A direct byte buffer may be created by invoking the allocateDirect factory 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.

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

献世佛 2024-08-20 19:54:54

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

buff.clear() 不是必需的,System.gc() 只有在像其他人已经提到的那样,不再有 ByteBuffer 对象的引用时才会有帮助。

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.

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.

撑一把青伞 2024-08-20 19:54:54

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

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

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);
        }
    }
}
终止放荡 2024-08-20 19:54:54

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

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 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);
        }
    }
}
刘备忘录 2024-08-20 19:54:54

这个问题的现有答案中缺少许多警告,例如在 JDK 9+ 下运行时要求模块描述符包含 requires jdk.unsupported,无法访问 MappedByteBuffer.cleaner( ) 由于强封装的强制执行,在 JDK 16+ 下没有解决方法,以及在 JDK 7-16 下使用 SecurityManager 运行的要求等。我在这里查看完整的详细信息:

< a href="https://stackoverflow.com/a/54046774/3950982">https://stackoverflow.com/a/54046774/3950982

There are many caveats missing from the existing answers to this question, e.g. requirements when running under JDK 9+ for the module descriptor to contain requires jdk.unsupported, the inability to access MappedByteBuffer.cleaner() without workarounds under JDK 16+ due to the enforcement of strong encapsulation, the requirements if running with a SecurityManager under JDK 7-16, etc. I go over the full details here:

https://stackoverflow.com/a/54046774/3950982

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文