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   * Allows to free direct {@link ByteBuffer} by using Cleaner. This is encapsulated in an extra class to be able
32   * to use {@link PlatformDependent0} on Android without problems.
33   * <p>
34   * For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>.
35   */
36  final class CleanerJava6 implements Cleaner {
37      private static final MethodHandle CLEAN_METHOD;
38  
39      private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);
40  
41      static {
42          MethodHandle clean;
43          Throwable error = null;
44          final ByteBuffer direct = ByteBuffer.allocateDirect(1);
45          try {
46              Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
47                  @Override
48                  public Object run() {
49                      try {
50                          Class<?> cleanerClass = Class.forName("sun.misc.Cleaner");
51                          Class<?> directBufClass = Class.forName("sun.nio.ch.DirectBuffer");
52                          MethodHandles.Lookup lookup = MethodHandles.lookup();
53  
54                          // Call clean() on the cleaner
55                          MethodHandle clean = lookup.findVirtual(
56                                  cleanerClass, "clean", methodType(void.class));
57                          // But only if the cleaner is non-null
58                          MethodHandle nullTest = lookup.findStatic(
59                                  Objects.class, "nonNull", methodType(boolean.class, Object.class));
60                          clean = MethodHandles.guardWithTest(
61                                  nullTest.asType(methodType(boolean.class, cleanerClass)),
62                                  clean,
63                                  nullTest.asType(methodType(void.class, cleanerClass)));
64                          // Change receiver to DirectBuffer, convert DirectBuffer to Cleaner by calling cleaner()
65                          clean = MethodHandles.filterArguments(clean, 0, lookup.findVirtual(
66                                  directBufClass,
67                                  "cleaner",
68                                  methodType(cleanerClass)));
69                          // Change receiver to ByteBuffer, convert using explicit cast to DirectBuffer
70                          clean = MethodHandles.explicitCastArguments(clean,
71                                  methodType(void.class, ByteBuffer.class));
72                          return clean;
73                      } catch (Throwable cause) {
74                          return cause;
75                      }
76                  }
77              });
78              if (mayBeCleanerField instanceof Throwable) {
79                  throw (Throwable) mayBeCleanerField;
80              }
81  
82              clean = (MethodHandle) mayBeCleanerField;
83              clean.invokeExact(direct);
84          } catch (Throwable t) {
85              // We don't have ByteBuffer.cleaner().
86              clean = null;
87              error = t;
88          }
89  
90          if (error == null) {
91              logger.debug("java.nio.ByteBuffer.cleaner(): available");
92          } else {
93              logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
94          }
95          CLEAN_METHOD = clean;
96      }
97  
98      static boolean isSupported() {
99          return CLEAN_METHOD != null;
100     }
101 
102     @Override
103     public CleanableDirectBuffer allocate(int capacity) {
104         return new CleanableDirectBufferImpl(ByteBuffer.allocateDirect(capacity));
105     }
106 
107     @Deprecated
108     @Override
109     public void freeDirectBuffer(ByteBuffer buffer) {
110         freeDirectBufferStatic(buffer);
111     }
112 
113     private static void freeDirectBufferStatic(ByteBuffer buffer) {
114         if (!buffer.isDirect()) {
115             return;
116         }
117         if (System.getSecurityManager() == null) {
118             try {
119                 freeDirectBuffer0(buffer);
120             } catch (Throwable cause) {
121                 PlatformDependent0.throwException(cause);
122             }
123         } else {
124             freeDirectBufferPrivileged(buffer);
125         }
126     }
127 
128     private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
129         Throwable cause = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
130             @Override
131             public Throwable run() {
132                 try {
133                     freeDirectBuffer0(buffer);
134                     return null;
135                 } catch (Throwable cause) {
136                     return cause;
137                 }
138             }
139         });
140         if (cause != null) {
141             PlatformDependent0.throwException(cause);
142         }
143     }
144 
145     private static void freeDirectBuffer0(ByteBuffer buffer) throws Throwable {
146         CLEAN_METHOD.invokeExact(buffer);
147     }
148 
149     private static final class CleanableDirectBufferImpl implements CleanableDirectBuffer {
150         private final ByteBuffer buffer;
151 
152         private CleanableDirectBufferImpl(ByteBuffer buffer) {
153             this.buffer = buffer;
154         }
155 
156         @Override
157         public ByteBuffer buffer() {
158             return buffer;
159         }
160 
161         @Override
162         public void clean() {
163             freeDirectBufferStatic(buffer);
164         }
165     }
166 }