From e90c9b385b993029688fce0fcf48d3c4bb593a01 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 24 May 2016 15:09:31 +0200 Subject: [PATCH] Attempt cleaning of DirectByteBuffer, #17194 --- .../scala/akka/io/DirectByteBufferPool.scala | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/akka-actor/src/main/scala/akka/io/DirectByteBufferPool.scala b/akka-actor/src/main/scala/akka/io/DirectByteBufferPool.scala index 84a9db4f04..fdde031dda 100644 --- a/akka-actor/src/main/scala/akka/io/DirectByteBufferPool.scala +++ b/akka-actor/src/main/scala/akka/io/DirectByteBufferPool.scala @@ -5,6 +5,7 @@ package akka.io import java.nio.ByteBuffer +import scala.util.control.NonFatal trait BufferPool { def acquire(): ByteBuffer @@ -54,11 +55,42 @@ private[akka] class DirectByteBufferPool(defaultBufferSize: Int, maxPoolEntries: } } - private final def offerBufferToPool(buf: ByteBuffer): Unit = - pool.synchronized { - if (buffersInPool < maxPoolEntries) { - pool(buffersInPool) = buf - buffersInPool += 1 - } // else let the buffer be gc'd + private final def offerBufferToPool(buf: ByteBuffer): Unit = { + val clean = + pool.synchronized { + if (buffersInPool < maxPoolEntries) { + pool(buffersInPool) = buf + buffersInPool += 1 + false + } else { + // try to clean it outside the lock, or let the buffer be gc'd + true + } + } + if (clean) + tryCleanDirectByteBuffer(buf) + } + + /** + * 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. + * + * Utilizes reflection to avoid dependency to `sun.misc.Cleaner`. + */ + private final def tryCleanDirectByteBuffer(toBeDestroyed: ByteBuffer): Unit = try { + if (toBeDestroyed.isDirect) { + val cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner") + cleanerMethod.setAccessible(true) + val cleaner = cleanerMethod.invoke(toBeDestroyed) + val cleanMethod = cleaner.getClass().getMethod("clean") + cleanMethod.setAccessible(true) + cleanMethod.invoke(cleaner) } + } catch { + case NonFatal(_) ⇒ // attempt failed, ok + } }