如何垃圾收集直接缓冲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 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); } } }