如何垃圾收集直接缓冲java

我有一个内存泄漏,我已经隔离到不正确的直接字节缓冲区。

  ByteBuffer buff = ByteBuffer.allocateDirect(7777777) 

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

  java.lang.OutOfMemoryError:直接缓冲内存 

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

  buff.clear 

  System.gc()的 

不起作用

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

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

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

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

ByteBuffer文档说:

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

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

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

buff.clear()不是必需的, System.gc()只会像其他已经提到的那样,在ByteBuffer对象中没有剩余的引用。

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

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

只要您依赖于sun(oracle)特定的实现,比尝试更改java.nio.DirectByteBuffer的可见性更好的选择是通过reflection使用sun.nio.ch.DirectBuffer接口。

 /** * Sun specific mechanisms to clean up resources associated with direct byte buffers. */ @SuppressWarnings("unchecked") private static final Class SUN_DIRECT_BUFFER = (Class) 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); } } }