Skip to content

Commit de99182

Browse files
authored
Merge pull request xerial#228 from bokken/master
java 9 valid direct byte buffer release
2 parents 820038c + 26eb048 commit de99182

File tree

1 file changed

+99
-22
lines changed

1 file changed

+99
-22
lines changed

src/main/java/org/xerial/snappy/SnappyFramed.java

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@
33
*/
44
package org.xerial.snappy;
55

6+
import static java.lang.invoke.MethodHandles.constant;
7+
import static java.lang.invoke.MethodHandles.dropArguments;
8+
import static java.lang.invoke.MethodHandles.filterReturnValue;
9+
import static java.lang.invoke.MethodHandles.guardWithTest;
10+
import static java.lang.invoke.MethodHandles.lookup;
11+
import static java.lang.invoke.MethodType.methodType;
12+
613
import java.io.IOException;
14+
import java.lang.invoke.MethodHandle;
15+
import java.lang.invoke.MethodHandles.Lookup;
16+
import java.lang.reflect.Field;
717
import java.lang.reflect.Method;
818
import java.nio.ByteBuffer;
919
import java.nio.channels.ReadableByteChannel;
20+
import java.security.AccessController;
21+
import java.security.PrivilegedExceptionAction;
1022
import java.util.logging.Level;
1123
import java.util.logging.Logger;
1224

@@ -30,27 +42,76 @@ final class SnappyFramed
3042
* Sun specific mechanisms to clean up resources associated with direct byte buffers.
3143
*/
3244
@SuppressWarnings("unchecked")
33-
private static final Class<? extends ByteBuffer> SUN_DIRECT_BUFFER = (Class<? extends ByteBuffer>) lookupClassQuietly("sun.nio.ch.DirectBuffer");
34-
private static final Method SUN_BUFFER_CLEANER;
35-
private static final Method SUN_CLEANER_CLEAN;
45+
static final Class<? extends ByteBuffer> DIRECT_BUFFER_CLAZZ = (Class<? extends ByteBuffer>) lookupClassQuietly("java.nio.DirectByteBuffer");
3646

47+
static final MethodHandle CLEAN_HANDLE;
48+
3749
static {
38-
Method bufferCleaner = null;
39-
Method cleanerClean = null;
50+
// this approach is based off that used by apache lucene and documented here: https://issues.apache.org/jira/browse/LUCENE-6989
51+
// and https://github.com/apache/lucene-solr/blob/7e03427fa14a024ce257babcb8362d2451941e21/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
52+
MethodHandle cleanHandle = null;
4053
try {
41-
//operate under the assumption that if the sun direct buffer class exists,
42-
//all of the sun classes exist
43-
if (SUN_DIRECT_BUFFER != null) {
44-
bufferCleaner = SUN_DIRECT_BUFFER.getMethod("cleaner", (Class[]) null);
45-
Class<?> cleanClazz = lookupClassQuietly("sun.misc.Cleaner");
46-
cleanerClean = cleanClazz.getMethod("clean", (Class[]) null);
47-
}
48-
}
49-
catch (Throwable t) {
54+
final PrivilegedExceptionAction<MethodHandle> action = new PrivilegedExceptionAction<MethodHandle>() {
55+
56+
@Override
57+
public MethodHandle run() throws Exception {
58+
MethodHandle handle = null;
59+
if (DIRECT_BUFFER_CLAZZ != null) {
60+
final Lookup lookup = lookup();
61+
62+
try {
63+
// sun.misc.Unsafe unmapping (Java 9+)
64+
final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
65+
// first check if Unsafe has the right method, otherwise we can give up
66+
// without doing any security critical stuff:
67+
final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class));
68+
// fetch the unsafe instance and bind it to the virtual MH:
69+
final Field f = unsafeClass.getDeclaredField("theUnsafe");
70+
f.setAccessible(true);
71+
final Object theUnsafe = f.get(null);
72+
handle = unmapper.bindTo(theUnsafe);
73+
} catch (Exception e) {
74+
Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "unable to use java 9 Unsafe.invokeCleaner", e);
75+
76+
// sun.misc.Cleaner unmapping (Java 8 and older)
77+
final Method m = DIRECT_BUFFER_CLAZZ.getMethod("cleaner");
78+
m.setAccessible(true);
79+
final MethodHandle directBufferCleanerMethod = lookup.unreflect(m);
80+
final Class<?> cleanerClass = directBufferCleanerMethod.type().returnType();
81+
82+
/*
83+
* "Compile" a MethodHandle that basically is equivalent to the following code:
84+
* void unmapper(ByteBuffer byteBuffer)
85+
* {
86+
* sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner();
87+
* if (nonNull(cleaner))
88+
* {
89+
* cleaner.clean();
90+
* }
91+
* else
92+
* {
93+
* // the noop is needed because MethodHandles#guardWithTest always needs ELSE
94+
* noop(cleaner);
95+
* }
96+
* }
97+
*/
98+
final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class));
99+
final MethodHandle nonNullTest = lookup.findStatic(SnappyFramed.class, "nonNull", methodType(boolean.class, Object.class)).asType(methodType(boolean.class, cleanerClass));
100+
final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass);
101+
handle = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop)).asType(methodType(void.class, ByteBuffer.class));
102+
}
103+
}
104+
105+
return handle;
106+
}
107+
};
108+
109+
cleanHandle = AccessController.doPrivileged(action);
110+
111+
} catch (Throwable t) {
50112
Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "Exception occurred attempting to lookup Sun specific DirectByteBuffer cleaner classes.", t);
51113
}
52-
SUN_BUFFER_CLEANER = bufferCleaner;
53-
SUN_CLEANER_CLEAN = cleanerClean;
114+
CLEAN_HANDLE = cleanHandle;
54115
}
55116

56117
/**
@@ -170,18 +231,34 @@ private static Class<?> lookupClassQuietly(String name)
170231
*
171232
* @param buffer The {@code ByteBuffer} to release. Must not be {@code null}. Must be {@link ByteBuffer#isDirect() direct}.
172233
*/
173-
static void releaseDirectByteBuffer(ByteBuffer buffer)
234+
static void releaseDirectByteBuffer(final ByteBuffer buffer)
174235
{
175236
assert buffer != null && buffer.isDirect();
176237

177-
if (SUN_DIRECT_BUFFER != null && SUN_DIRECT_BUFFER.isAssignableFrom(buffer.getClass())) {
238+
if (CLEAN_HANDLE != null && DIRECT_BUFFER_CLAZZ.isInstance(buffer)) {
178239
try {
179-
Object cleaner = SUN_BUFFER_CLEANER.invoke(buffer, (Object[]) null);
180-
SUN_CLEANER_CLEAN.invoke(cleaner, (Object[]) null);
181-
}
182-
catch (Throwable t) {
240+
final PrivilegedExceptionAction<Void> pea = new PrivilegedExceptionAction<Void>() {
241+
@Override
242+
public Void run() throws Exception {
243+
try {
244+
CLEAN_HANDLE.invokeExact(buffer);
245+
} catch (Exception e) {
246+
throw e;
247+
} catch (Throwable t) {
248+
//this will be an error
249+
throw new RuntimeException(t);
250+
}
251+
return null;
252+
}
253+
};
254+
AccessController.doPrivileged(pea);
255+
} catch (Throwable t) {
183256
Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t);
184257
}
185258
}
186259
}
260+
261+
static boolean nonNull(Object o) {
262+
return o != null;
263+
}
187264
}

0 commit comments

Comments
 (0)