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