概要
JDK9では、JDK-8148117などによって、sun.misc.Cleaner
がjdf.internal.ref.Cleaner
に移動してしまった。JDK9だけで動かす前提なら単純にimport文を書き換えれば良いが、JDK9とJDK8の両方でビルドを通したい場合は黒魔術的な対処が必要になる。以下、この黒魔術について解説する。
目的
[HADOOP-12760]sun.misc.Cleaner has moved to a new location in OpenJDK 9をなんとかしたい
詳細
問題となっているコード
/**
* Unmaps the block from memory. See munmap(2).
*
* There isn't any portable way to unmap a memory region in Java.
* So we use the sun.nio method here.
* Note that unmapping a memory region could cause crashes if code
* continues to reference the unmapped code. However, if we don't
* manually unmap the memory, we are dependent on the finalizer to
* do it, and we have no idea when the finalizer will run.
*
* @param buffer The buffer to unmap.
*/
public static void munmap(MappedByteBuffer buffer) {
if (buffer instanceof sun.nio.ch.DirectBuffer) {
sun.misc.Cleaner cleaner =
((sun.nio.ch.DirectBuffer)buffer).cleaner();
cleaner.clean();
}
}
どうでもいいコメント
-
mlockはCのコードを呼び出しているのにmunmapがそうしていないのは謎
- 過去の経緯(HDFS-4953)を読むと、JNIへの依存を避けてSun-specific APIを使うことにしたようだが、他のコードはガンガンJNIを使っているので、一貫性がない
- (追記) munmapはWindowsで動作させられないので、Javaで書いている
JDK9ではsun.misc.Cleaner
が移動してしまっているので、このコードは動かない。そのため、以下のようなコードで対応する。
private static MethodHandle cleanMethod = null;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
final Class<?> directBufferClass =
Class.forName("java.nio.DirectByteBuffer");
final Method m = directBufferClass.getMethod("cleaner");
// Set permission to the lookup class for making
// a direct method handle
m.setAccessible(true);
// Make a direct method handle
MethodHandle directbufferCleanerMethod = lookup.unreflect(m);
// Get the return type of java.nio.DirectByteBuffer
// JDK8: sun.misc.Cleaner
// JDK9: jdk.internal.ref.Cleaner
Class<?> cleanerClass = directbufferCleanerMethod.type().returnType();
// Get the method (Cleaner#clean)
cleanMethod = lookup.findVirtual(
cleanerClass, "clean", methodType(void.class));
} catch (NoSuchMethodException | IllegalAccessException |
ClassNotFoundException ignored) {
}
}
http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/07dcdd5d9401 によると、java.nio.DirectByteBuffer#cleaner
の戻り値が
- JDK8:
sun.misc.Cleaner
- JDK9:
jdk.internal.ref.Cleaner
になっているので、これを呼び出している。munmapメソッドは以下のように書き換えられる。
public static void munmap(MappedByteBuffer buffer) {
if (buffer instanceof sun.nio.ch.DirectBuffer) {
if (cleanMethod != null) {
try {
// Execute Cleaner#clean
cleanMethod.invokeExact(
((sun.nio.ch.DirectBuffer)buffer).cleaner());
} catch (Throwable ignored) {
}
}
Class.forName
でsun.misc.Cleaner
もしくはjdk.internal.ref.Cleaner
を直接呼び出してもいいのでは、という案もあるが、jdk.internal.ref.Cleaner
がいつまで居座っているかもわからないのでこういった手法をとっている。
参考
同様の手法(黒魔術)は、Apache Luceneで既に実施されており、本記事はこれに基づいたものである。
- Apache Luceneにおける対処: MMapDirectory.java (GitHub)