1 package org.xerial.snappy.pool;
2 
3 import static java.lang.invoke.MethodHandles.constant;
4 import static java.lang.invoke.MethodHandles.dropArguments;
5 import static java.lang.invoke.MethodHandles.filterReturnValue;
6 import static java.lang.invoke.MethodHandles.guardWithTest;
7 import static java.lang.invoke.MethodHandles.lookup;
8 import static java.lang.invoke.MethodType.methodType;
9 
10 import java.lang.invoke.MethodHandle;
11 import java.lang.invoke.MethodHandles.Lookup;
12 import java.lang.reflect.Field;
13 import java.lang.reflect.Method;
14 import java.nio.ByteBuffer;
15 import java.security.AccessController;
16 import java.security.PrivilegedExceptionAction;
17 import java.util.logging.Level;
18 import java.util.logging.Logger;
19 
20 /**
21  * Utility to facilitate disposing of direct byte buffer instances.
22  */
23 final class DirectByteBuffers {
24 
25     /**
26      * Sun specific mechanisms to clean up resources associated with direct byte buffers.
27      */
28     @SuppressWarnings("unchecked")
29     static final Class<? extends ByteBuffer> DIRECT_BUFFER_CLAZZ = (Class<? extends ByteBuffer>) lookupClassQuietly("java.nio.DirectByteBuffer");
30 
31     static final MethodHandle CLEAN_HANDLE;
32 
33     static {
34         // this approach is based off that used by apache lucene and documented here: https://issues.apache.org/jira/browse/LUCENE-6989
35         // and https://github.com/apache/lucene-solr/blob/7e03427fa14a024ce257babcb8362d2451941e21/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java
36         MethodHandle cleanHandle = null;
37         try {
38             final PrivilegedExceptionAction<MethodHandle> action = new PrivilegedExceptionAction<MethodHandle>() {
39 
40                 @Override
41                 public MethodHandle run() throws Exception {
42                     MethodHandle handle = null;
43                     if (DIRECT_BUFFER_CLAZZ != null) {
44                         final Lookup lookup = lookup();
45 
46                         try {
47                             // sun.misc.Unsafe unmapping (Java 9+)
48                             final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
49                             // first check if Unsafe has the right method, otherwise we can give up
50                             // without doing any security critical stuff:
51                             final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class));
52                             // fetch the unsafe instance and bind it to the virtual MH:
53                             final Field f = unsafeClass.getDeclaredField("theUnsafe");
54                             f.setAccessible(true);
55                             final Object theUnsafe = f.get(null);
56                             handle = unmapper.bindTo(theUnsafe);
57                         } catch (Exception e) {
58                             Logger.getLogger(DirectByteBuffers.class.getName()).log(Level.FINE, "unable to use java 9 Unsafe.invokeCleaner", e);
59 
60                             // sun.misc.Cleaner unmapping (Java 8 and older)
61                             final Method m = DIRECT_BUFFER_CLAZZ.getMethod("cleaner");
62                             m.setAccessible(true);
63                             final MethodHandle directBufferCleanerMethod = lookup.unreflect(m);
64                             final Class<?> cleanerClass = directBufferCleanerMethod.type().returnType();
65 
66                             /*
67                              * "Compile" a MethodHandle that basically is equivalent to the following code:
68                              *  void unmapper(ByteBuffer byteBuffer)
69                              *  {
70                              *      sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner();
71                              *      if (nonNull(cleaner))
72                              *      {
73                              *          cleaner.clean();
74                              *      }
75                              *      else
76                              *      {
77                              *          // the noop is needed because MethodHandles#guardWithTest always needs ELSE
78                              *          noop(cleaner);
79                              *      }
80                              *  }
81                              */
82                             final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class));
83                             final MethodHandle nonNullTest = lookup.findStatic(DirectByteBuffers.class, "nonNull", methodType(boolean.class, Object.class)).asType(methodType(boolean.class, cleanerClass));
84                             final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass);
85                             handle = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop)).asType(methodType(void.class, ByteBuffer.class));
86                         }
87                     }
88 
89                     return handle;
90                 }
91             };
92 
93             cleanHandle = AccessController.doPrivileged(action);
94 
95         } catch (Throwable t) {
96             Logger.getLogger(DirectByteBuffers.class.getName()).log(Level.FINE, "Exception occurred attempting to lookup Sun specific DirectByteBuffer cleaner classes.", t);
97         }
98         CLEAN_HANDLE = cleanHandle;
99     }
100 
101 
lookupClassQuietly(String name)102     private static Class<?> lookupClassQuietly(String name)
103     {
104         try {
105             return DirectByteBuffers.class.getClassLoader().loadClass(name);
106         }
107         catch (Throwable t) {
108             Logger.getLogger(DirectByteBuffers.class.getName()).log(Level.FINE, "Did not find requested class: " + name, t);
109         }
110 
111         return null;
112     }
113 
114 
nonNull(Object o)115     static boolean nonNull(Object o) {
116         return o != null;
117     }
118 
119     /**
120      * Provides jvm implementation specific operation to aggressively release resources associated with <i>buffer</i>.
121      *
122      * @param buffer The {@code ByteBuffer} to release. Must not be {@code null}. Must be  {@link ByteBuffer#isDirect() direct}.
123      */
releaseDirectByteBuffer(final ByteBuffer buffer)124     public static void releaseDirectByteBuffer(final ByteBuffer buffer)
125     {
126         assert buffer != null && buffer.isDirect();
127 
128         if (CLEAN_HANDLE != null && DIRECT_BUFFER_CLAZZ.isInstance(buffer)) {
129             try {
130                 final PrivilegedExceptionAction<Void> pea = new PrivilegedExceptionAction<Void>() {
131                     @Override
132                     public Void run() throws Exception {
133                         try {
134                             CLEAN_HANDLE.invokeExact(buffer);
135                         } catch (Exception e) {
136                             throw e;
137                         } catch (Throwable t) {
138                             //this will be an error
139                             throw new RuntimeException(t);
140                         }
141                         return null;
142                     }
143                 };
144                 AccessController.doPrivileged(pea);
145             } catch (Throwable t) {
146                 Logger.getLogger(DirectByteBuffers.class.getName()).log(Level.FINE, "Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t);
147             }
148         }
149     }
150 }
151