如何垃圾收集直接缓冲java

时间:2009-12-06 04:51:27

标签: java memory-leaks buffer

我有一个内存泄漏,我已经隔离到错误配置的直接字节缓冲区。

ByteBuffer buff = ByteBuffer.allocateDirect(7777777)

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

java.lang.OutOfMemoryError: Direct buffer memory

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

buff.clear

 System.gc()

不起作用

6 个答案:

答案 0 :(得分:16)

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

}

答案 1 :(得分:14)

我怀疑你的应用程序的某个地方引用了ByteBuffer实例,并且阻止它被垃圾收集。

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

答案 2 :(得分:12)

ByteBuffer文档说:

  

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

特别是,声明“可能位于正常的垃圾收集堆之外”似乎与您的示例相关。

答案 3 :(得分:5)

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

buff.clear()不是必需的,System.gc()只有在像其他人已经提到的那样,没有更多的参考留给ByteBuffer对象时才有用。

答案 4 :(得分:2)

这是一个精确的实现,适用于任何直接缓冲区:

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

答案 5 :(得分:1)

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