View Javadoc
1   /*
2   * Copyright 2014 The Netty Project
3   *
4   * The Netty Project licenses this file to you under the Apache License,
5   * version 2.0 (the "License"); you may not use this file except in compliance
6   * with the License. You may obtain a copy of the License at:
7   *
8   *   https://www.apache.org/licenses/LICENSE-2.0
9   *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16  package io.netty.util.internal;
17  
18  import io.netty.util.internal.logging.InternalLogger;
19  import io.netty.util.internal.logging.InternalLoggerFactory;
20  
21  import java.lang.invoke.MethodHandle;
22  import java.lang.invoke.MethodHandles;
23  import java.nio.ByteBuffer;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  import java.util.Objects;
27  
28  import static java.lang.invoke.MethodType.methodType;
29  
30  
31  /**
32   * Allows to free direct {@link ByteBuffer} by using Cleaner. This is encapsulated in an extra class to be able
33   * to use {@link PlatformDependent0} on Android without problems.
34   * <p>
35   * For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>.
36   */
37  final class CleanerJava6 implements Cleaner {
38      private static final MethodHandle CLEAN_METHOD;
39  
40      private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);
41  
42      static {
43          MethodHandle clean;
44          Throwable error = null;
45          final ByteBuffer direct = ByteBuffer.allocateDirect(1);
46          try {
47              Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
48                  @Override
49                  public Object run() {
50                      try {
51                          Class<?> cleanerClass = Class.forName("sun.misc.Cleaner");
52                          Class<?> directBufClass = Class.forName("sun.nio.ch.DirectBuffer");
53                          MethodHandles.Lookup lookup = MethodHandles.lookup();
54  
55                          // Call clean() on the cleaner
56                          MethodHandle clean = lookup.findVirtual(
57                                  cleanerClass, "clean", methodType(void.class));
58                          // But only if the cleaner is non-null
59                          MethodHandle nullTest = lookup.findStatic(
60                                  Objects.class, "nonNull", methodType(boolean.class, Object.class));
61                          clean = MethodHandles.guardWithTest(
62                                  nullTest.asType(methodType(boolean.class, cleanerClass)),
63                                  clean,
64                                  nullTest.asType(methodType(void.class, cleanerClass)));
65                          // Change receiver to DirectBuffer, convert DirectBuffer to Cleaner by calling cleaner()
66                          clean = MethodHandles.filterArguments(clean, 0, lookup.findVirtual(
67                                  directBufClass,
68                                  "cleaner",
69                                  methodType(cleanerClass)));
70                          // Change receiver to ByteBuffer, convert using explicit cast to DirectBuffer
71                          clean = MethodHandles.explicitCastArguments(clean,
72                                  methodType(void.class, ByteBuffer.class));
73                          return clean;
74                      } catch (Throwable cause) {
75                          return cause;
76                      }
77                  }
78              });
79              if (mayBeCleanerField instanceof Throwable) {
80                  throw (Throwable) mayBeCleanerField;
81              }
82  
83              clean = (MethodHandle) mayBeCleanerField;
84              clean.invokeExact(direct);
85          } catch (Throwable t) {
86              // We don't have ByteBuffer.cleaner().
87              clean = null;
88              error = t;
89          }
90  
91          if (error == null) {
92              logger.debug("java.nio.ByteBuffer.cleaner(): available");
93          } else {
94              logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
95          }
96          CLEAN_METHOD = clean;
97      }
98  
99      static boolean isSupported() {
100         return CLEAN_METHOD != null;
101     }
102 
103     @Override
104     public void freeDirectBuffer(ByteBuffer buffer) {
105         if (!buffer.isDirect()) {
106             return;
107         }
108         if (System.getSecurityManager() == null) {
109             try {
110                 freeDirectBuffer0(buffer);
111             } catch (Throwable cause) {
112                 PlatformDependent0.throwException(cause);
113             }
114         } else {
115             freeDirectBufferPrivileged(buffer);
116         }
117     }
118 
119     private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
120         Throwable cause = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
121             @Override
122             public Throwable run() {
123                 try {
124                     freeDirectBuffer0(buffer);
125                     return null;
126                 } catch (Throwable cause) {
127                     return cause;
128                 }
129             }
130         });
131         if (cause != null) {
132             PlatformDependent0.throwException(cause);
133         }
134     }
135 
136     private static void freeDirectBuffer0(ByteBuffer buffer) throws Throwable {
137         CLEAN_METHOD.invokeExact(buffer);
138     }
139 }