採用している他の回答でカバーされている方法は、警告((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
ます。が他のすべてのメソッドと同じ方法でプロキシされない理由はわかりません。JDK 15 の時点でinvokeCleaner(ByteBuffer)
メモリ参照 (インスタンスを含む) を直接解放する新しい方法があるため、省略された可能性があります。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")
ます。
ガベージ コレクション aMappedByteBuffer
または aのより完全なメソッドDirectByteBuffer
が ClassGraph に実装されています (私は作成者です)。エントリ ポイントは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 ドライバーを呼び出すことにより、カプセル化されたプライベート メソッドへのアクセスを抽象化します。
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