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.reflect.Field;
22  import java.lang.reflect.Method;
23  import java.nio.ByteBuffer;
24  import java.security.AccessController;
25  import java.security.PrivilegedAction;
26  
27  
28  /**
29   * Allows to free direct {@link ByteBuffer} by using Cleaner. This is encapsulated in an extra class to be able
30   * to use {@link PlatformDependent0} on Android without problems.
31   *
32   * For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>.
33   */
34  final class CleanerJava6 implements Cleaner {
35      private static final long CLEANER_FIELD_OFFSET;
36      private static final Method CLEAN_METHOD;
37      private static final Field CLEANER_FIELD;
38  
39      private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);
40  
41      static {
42          long fieldOffset;
43          Method clean;
44          Field cleanerField;
45          Throwable error = null;
46          final ByteBuffer direct = ByteBuffer.allocateDirect(1);
47          try {
48              Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
49                  @Override
50                  public Object run() {
51                      try {
52                          Field cleanerField =  direct.getClass().getDeclaredField("cleaner");
53                          if (!PlatformDependent.hasUnsafe()) {
54                              // We need to make it accessible if we do not use Unsafe as we will access it via
55                              // reflection.
56                              cleanerField.setAccessible(true);
57                          }
58                          return cleanerField;
59                      } catch (Throwable cause) {
60                          return cause;
61                      }
62                  }
63              });
64              if (mayBeCleanerField instanceof Throwable) {
65                  throw (Throwable) mayBeCleanerField;
66              }
67  
68              cleanerField = (Field) mayBeCleanerField;
69  
70              final Object cleaner;
71  
72              // If we have sun.misc.Unsafe we will use it as its faster then using reflection,
73              // otherwise let us try reflection as last resort.
74              if (PlatformDependent.hasUnsafe()) {
75                  fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
76                  cleaner = PlatformDependent0.getObject(direct, fieldOffset);
77              } else {
78                  fieldOffset = -1;
79                  cleaner = cleanerField.get(direct);
80              }
81              clean = cleaner.getClass().getDeclaredMethod("clean");
82              clean.invoke(cleaner);
83          } catch (Throwable t) {
84              // We don't have ByteBuffer.cleaner().
85              fieldOffset = -1;
86              clean = null;
87              error = t;
88              cleanerField = null;
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          CLEANER_FIELD = cleanerField;
97          CLEANER_FIELD_OFFSET = fieldOffset;
98          CLEAN_METHOD = clean;
99      }
100 
101     static boolean isSupported() {
102         return CLEANER_FIELD_OFFSET != -1 || CLEANER_FIELD != null;
103     }
104 
105     @Override
106     public void freeDirectBuffer(ByteBuffer buffer) {
107         if (!buffer.isDirect()) {
108             return;
109         }
110         if (System.getSecurityManager() == null) {
111             try {
112                 freeDirectBuffer0(buffer);
113             } catch (Throwable cause) {
114                 PlatformDependent0.throwException(cause);
115             }
116         } else {
117             freeDirectBufferPrivileged(buffer);
118         }
119     }
120 
121     private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
122         Throwable cause = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
123             @Override
124             public Throwable run() {
125                 try {
126                     freeDirectBuffer0(buffer);
127                     return null;
128                 } catch (Throwable cause) {
129                     return cause;
130                 }
131             }
132         });
133         if (cause != null) {
134             PlatformDependent0.throwException(cause);
135         }
136     }
137 
138     private static void freeDirectBuffer0(ByteBuffer buffer) throws Exception {
139         final Object cleaner;
140         // If CLEANER_FIELD_OFFSET == -1 we need to use reflection to access the cleaner, otherwise we can use
141         // sun.misc.Unsafe.
142         if (CLEANER_FIELD_OFFSET == -1) {
143             cleaner = CLEANER_FIELD.get(buffer);
144         } else {
145             cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
146         }
147         if (cleaner != null) {
148             CLEAN_METHOD.invoke(cleaner);
149         }
150     }
151 }