Метод, описанный в других ответах, в котором используется ((DirectBuffer) byteBuffer).cleaner().clean()
, не работает с JDK 9+ (даже в отражающей форме) без отображения предупреждения An illegal reflective access operation has occurred
. Это перестанет работать вообще в некоторых будущих версиях JDK. К счастью, sun.misc.Unsafe.invokeCleaner(ByteBuffer)
может сделать тот же самый вызов для вас без предупреждения: (из источника OpenJDK 11):
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();
}
}
Будучи sun.misc
классом, он будет удален в какой-то момент. Интересно, что все вызовы, кроме этого в sun.misc.Unsafe
, проксируются напрямую на jdk.internal.misc.Unsafe
(я не знаю, почему invokeCleaner(ByteBuffer)
не проксируется так же, как и все другие методы - это, вероятно, было случайное упущение).
Я написал следующий код, который может очищать / закрывать / отменять отображение экземпляров DirectByteBuffer / MappedByteBuffer на JDK 7/8, а также на JDK 9+, и это не выдает предупреждение об отражении:
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;
}
}
Обратите внимание, что вам нужно будет добавить requires jdk.unsupported
в дескриптор вашего модуля в модульной среде выполнения на JDK 9+ (необходим для использования Unsafe
).
Ваша банка может также потребовать RuntimePermission("accessClassInPackage.sun.misc")
, RuntimePermission("accessClassInPackage.jdk.internal.misc")
и ReflectPermission("suppressAccessChecks")
.