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     @Override
114     public boolean hasExpensiveClean() {
115         return false;
116     }
117 
118     private static void freeDirectBufferStatic(ByteBuffer buffer) {
119         if (!buffer.isDirect()) {
120             return;
121         }
122         if (System.getSecurityManager() == null) {
123             try {
124                 freeDirectBuffer0(buffer);
125             } catch (Throwable cause) {
126                 PlatformDependent0.throwException(cause);
127             }
128         } else {
129             freeDirectBufferPrivileged(buffer);
130         }
131     }
132 
133     private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
134         Throwable cause = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
135             @Override
136             public Throwable run() {
137                 try {
138                     freeDirectBuffer0(buffer);
139                     return null;
140                 } catch (Throwable cause) {
141                     return cause;
142                 }
143             }
144         });
145         if (cause != null) {
146             PlatformDependent0.throwException(cause);
147         }
148     }
149 
150     private static void freeDirectBuffer0(ByteBuffer buffer) throws Throwable {
151         CLEAN_METHOD.invokeExact(buffer);
152     }
153 
154     private static final class CleanableDirectBufferImpl implements CleanableDirectBuffer {
155         private final ByteBuffer buffer;
156 
157         private CleanableDirectBufferImpl(ByteBuffer buffer) {
158             this.buffer = buffer;
159             PlatformDependent.incrementMemoryCounter(buffer.capacity());
160         }
161 
162         @Override
163         public ByteBuffer buffer() {
164             return buffer;
165         }
166 
167         @Override
168         public void clean() {
169             int capacity = buffer.capacity();
170             freeDirectBufferStatic(buffer);
171             PlatformDependent.decrementMemoryCounter(capacity);
172         }
173     }
174 }