3
3
*/
4
4
package org .xerial .snappy ;
5
5
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
+
6
13
import java .io .IOException ;
14
+ import java .lang .invoke .MethodHandle ;
15
+ import java .lang .invoke .MethodHandles .Lookup ;
16
+ import java .lang .reflect .Field ;
7
17
import java .lang .reflect .Method ;
8
18
import java .nio .ByteBuffer ;
9
19
import java .nio .channels .ReadableByteChannel ;
20
+ import java .security .AccessController ;
21
+ import java .security .PrivilegedExceptionAction ;
10
22
import java .util .logging .Level ;
11
23
import java .util .logging .Logger ;
12
24
@@ -30,27 +42,76 @@ final class SnappyFramed
30
42
* Sun specific mechanisms to clean up resources associated with direct byte buffers.
31
43
*/
32
44
@ 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" );
36
46
47
+ static final MethodHandle CLEAN_HANDLE ;
48
+
37
49
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 ;
40
53
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 ) {
50
112
Logger .getLogger (SnappyFramed .class .getName ()).log (Level .FINE , "Exception occurred attempting to lookup Sun specific DirectByteBuffer cleaner classes." , t );
51
113
}
52
- SUN_BUFFER_CLEANER = bufferCleaner ;
53
- SUN_CLEANER_CLEAN = cleanerClean ;
114
+ CLEAN_HANDLE = cleanHandle ;
54
115
}
55
116
56
117
/**
@@ -170,18 +231,34 @@ private static Class<?> lookupClassQuietly(String name)
170
231
*
171
232
* @param buffer The {@code ByteBuffer} to release. Must not be {@code null}. Must be {@link ByteBuffer#isDirect() direct}.
172
233
*/
173
- static void releaseDirectByteBuffer (ByteBuffer buffer )
234
+ static void releaseDirectByteBuffer (final ByteBuffer buffer )
174
235
{
175
236
assert buffer != null && buffer .isDirect ();
176
237
177
- if (SUN_DIRECT_BUFFER != null && SUN_DIRECT_BUFFER . isAssignableFrom (buffer . getClass () )) {
238
+ if (CLEAN_HANDLE != null && DIRECT_BUFFER_CLAZZ . isInstance (buffer )) {
178
239
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 ) {
183
256
Logger .getLogger (SnappyFramed .class .getName ()).log (Level .FINE , "Exception occurred attempting to clean up Sun specific DirectByteBuffer." , t );
184
257
}
185
258
}
186
259
}
260
+
261
+ static boolean nonNull (Object o ) {
262
+ return o != null ;
263
+ }
187
264
}
0 commit comments