The method covered in other answers that employs ((DirectBuffer) byteBuffer).cleaner().clean()
doesn't work on JDK 9+ (even in reflective form) without displaying an An illegal reflective access operation has occurred
warning. This will stop working altogether in some future JDK version. Fortunately sun.misc.Unsafe.invokeCleaner(ByteBuffer)
can make that exact same call for you without the warning: (from OpenJDK 11 source):
public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
if (!directBuffer.isDirect())
throw new IllegalArgumentException("buffer is non-direct");
DirectBuffer db = (DirectBuffer)directBuffer;
if (db.attachment() != null)
throw new IllegalArgumentException("duplicate or slice");
Cleaner cleaner = db.cleaner();
if (cleaner != null) {
cleaner.clean();
}
}
Being a sun.misc
class, it will be removed at some point. Interestingly all calls but this one in sun.misc.Unsafe
are proxied directly to jdk.internal.misc.Unsafe
. I don't know why invokeCleaner(ByteBuffer)
is not proxied in the same way as all the other methods -- it was probably omitted because there will be a new way to directly free memory references (including DirectByteBuffer
instances) as of about JDK 15.
I wrote the following code that is able to clean/close/unmap DirectByteBuffer/MappedByteBuffer instances on JDK 7/8, as well as JDK 9+, and this does not give the reflection warning:
private static boolean PRE_JAVA_9 =
System.getProperty("java.specification.version","9").startsWith("1.");
private static Method cleanMethod;
private static Method attachmentMethod;
private static Object theUnsafe;
static void getCleanMethodPrivileged() {
if (PRE_JAVA_9) {
try {
cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
cleanMethod.setAccessible(true);
final Class<?> directByteBufferClass =
Class.forName("sun.nio.ch.DirectBuffer");
attachmentMethod = directByteBufferClass.getMethod("attachment");
attachmentMethod.setAccessible(true);
} catch (final Exception ex) {
}
} else {
try {
Class<?> unsafeClass;
try {
unsafeClass = Class.forName("sun.misc.Unsafe");
} catch (Exception e) {
// jdk.internal.misc.Unsafe doesn't yet have invokeCleaner(),
// but that method should be added if sun.misc.Unsafe is removed.
unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
}
cleanMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
cleanMethod.setAccessible(true);
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
theUnsafe = theUnsafeField.get(null);
} catch (final Exception ex) {
}
}
}
static {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
getCleanMethodPrivileged();
return null;
}
});
}
private static boolean closeDirectByteBufferPrivileged(
final ByteBuffer byteBuffer, final LogNode log) {
try {
if (cleanMethod == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, cleanMethod == null");
}
return false;
}
if (PRE_JAVA_9) {
if (attachmentMethod == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, attachmentMethod == null");
}
return false;
}
// Make sure duplicates and slices are not cleaned, since this can result in
// duplicate attempts to clean the same buffer, which trigger a crash with:
// "A fatal error has been detected by the Java Runtime Environment:
// EXCEPTION_ACCESS_VIOLATION"
// See: https://stackoverflow.com/a/31592947/3950982
if (attachmentMethod.invoke(byteBuffer) != null) {
// Buffer is a duplicate or slice
return false;
}
// Invoke ((DirectBuffer) byteBuffer).cleaner().clean()
final Method cleaner = byteBuffer.getClass().getMethod("cleaner");
cleaner.setAccessible(true);
cleanMethod.invoke(cleaner.invoke(byteBuffer));
return true;
} else {
if (theUnsafe == null) {
if (log != null) {
log.log("Could not unmap ByteBuffer, theUnsafe == null");
}
return false;
}
// In JDK9+, calling the above code gives a reflection warning on stderr,
// need to call Unsafe.theUnsafe.invokeCleaner(byteBuffer) , which makes
// the same call, but does not print the reflection warning.
try {
cleanMethod.invoke(theUnsafe, byteBuffer);
return true;
} catch (final IllegalArgumentException e) {
// Buffer is a duplicate or slice
return false;
}
}
} catch (final Exception e) {
if (log != null) {
log.log("Could not unmap ByteBuffer: " + e);
}
return false;
}
}
/**
* Close a {@code DirectByteBuffer} -- in particular, will unmap a
* {@link MappedByteBuffer}.
*
* @param byteBuffer
* The {@link ByteBuffer} to close/unmap.
* @param log
* The log.
* @return True if the byteBuffer was closed/unmapped (or if the ByteBuffer
* was null or non-direct).
*/
public static boolean closeDirectByteBuffer(final ByteBuffer byteBuffer,
final Log log) {
if (byteBuffer != null && byteBuffer.isDirect()) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return closeDirectByteBufferPrivileged(byteBuffer, log);
}
});
} else {
// Nothing to unmap
return false;
}
}
Note that you will need to add the requires jdk.unsupported
to your module descriptor in a modular runtime on JDK 9+ (needed for the use of Unsafe
).
Your jar may also need RuntimePermission("accessClassInPackage.sun.misc")
, RuntimePermission("accessClassInPackage.jdk.internal.misc")
, and ReflectPermission("suppressAccessChecks")
.
A more complete method for garbage collecting a MappedByteBuffer
or a DirectByteBuffer
is implemented in ClassGraph (I am the author) -- the entry point is the closeDirectByteBuffer()
method at the end of FileUtils
:
https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/utils/FileUtils.java#L543
This code is written to use reflection, since the Java APIs that are used (including Unsafe
) are going away in the near future.
Note that there's an additional problem in JDK 16+:
this code will not work out of the box in JDK 16+ unless you use the Narcissus or JVM-Driver libraries to circumvent strong encapsulation. This is because MappedByteBuffer.clean()
is a private method, and JDK 16 enforces strong encapsulation. ClassGraph abstracts away access to private encapsulated methods by calling Narcissus or JVM-driver through reflection at runtime:
https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/reflection/ReflectionUtils.java
Warning: if you try to access the DirectByteBuffer
after it is cleaned (freed), it will crash the VM.
There are other security considerations, discussed in the last comment in this bug report:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-4724038