在不显示警告的情况下,其他答案中涵盖的方法((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)
没有以与所有其他方法相同的方式进行代理——它可能被省略了,因为从 JDK 15 开始会有一种直接释放内存引用(包括DirectByteBuffer
实例)的新方法。
我编写了以下代码,能够在 JDK 7/8 以及 JDK 9+ 上清理/关闭/取消映射 DirectByteBuffer/MappedByteBuffer 实例,但这不会给出反射警告:
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
)。
您的 jar 可能还需要RuntimePermission("accessClassInPackage.sun.misc")
,RuntimePermission("accessClassInPackage.jdk.internal.misc")
和ReflectPermission("suppressAccessChecks")
.
在ClassGraph中实现了一个更完整的垃圾收集aMappedByteBuffer
或aDirectByteBuffer
的方法(我是作者)——入口点是closeDirectByteBuffer()
末尾的方法FileUtils
:
https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/utils/FileUtils.java#L543
这段代码是为了使用反射而编写的,因为使用的 Java API(包括Unsafe
)将在不久的将来消失。
请注意,JDK 16+ 中还有一个问题:
除非您使用Narcissus或JVM-Driver库来规避强封装,否则此代码将无法在 JDK 16+ 中开箱即用。这是因为MappedByteBuffer.clean()
是私有方法,而 JDK 16 强制执行强封装。ClassGraph 通过在运行时通过反射调用 Narcissus 或 JVM-driver 来抽象出对私有封装方法的访问:
https://github.com/classgraph/classgraph/blob/latest/src/main/java/nonapi/io/github/classgraph/reflection/ReflectionUtils.java
警告:如果您在清理(释放)后尝试访问DirectByteBuffer
它,它将使 VM 崩溃。
此错误报告的最后一条评论中讨论了其他安全注意事项:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-4724038