View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.io.input;
18  
19  import java.lang.reflect.Field;
20  import java.lang.reflect.Method;
21  import java.nio.ByteBuffer;
22  
23  /**
24   * Cleans a direct {@link ByteBuffer}. Without manual intervention, direct ByteBuffers will be cleaned eventually upon
25   * garbage collection. However, this should not be relied upon since it may not occur in a timely fashion -
26   * especially since off heap ByeBuffers don't put pressure on the garbage collector.
27   * <p>
28   * <b>Warning:</b> Do not attempt to use a direct {@link ByteBuffer} that has been cleaned or bad things will happen.
29   * Don't use this class unless you can ensure that the cleaned buffer will not be accessed anymore.
30   * </p>
31   * <p>
32   * See <a href=https://bugs.openjdk.java.net/browse/JDK-4724038>JDK-4724038</a>
33   * </p>
34   */
35  final class ByteBufferCleaner {
36  
37      private interface Cleaner {
38          void clean(ByteBuffer buffer) throws ReflectiveOperationException;
39      }
40  
41      private static final class Java8Cleaner implements Cleaner {
42  
43          private final Method cleanerMethod;
44          private final Method cleanMethod;
45  
46          private Java8Cleaner() throws ReflectiveOperationException, SecurityException {
47              cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
48              cleanerMethod = Class.forName("sun.nio.ch.DirectBuffer").getMethod("cleaner");
49          }
50  
51          @Override
52          public void clean(final ByteBuffer buffer) throws ReflectiveOperationException {
53              final Object cleaner = cleanerMethod.invoke(buffer);
54              if (cleaner != null) {
55                  cleanMethod.invoke(cleaner);
56              }
57          }
58      }
59  
60      private static final class Java9Cleaner implements Cleaner {
61  
62          private final Object theUnsafe;
63          private final Method invokeCleaner;
64  
65          private Java9Cleaner() throws ReflectiveOperationException, SecurityException {
66              final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
67              final Field field = unsafeClass.getDeclaredField("theUnsafe");
68              field.setAccessible(true);
69              theUnsafe = field.get(null);
70              invokeCleaner = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
71          }
72  
73          @Override
74          public void clean(final ByteBuffer buffer) throws ReflectiveOperationException {
75              invokeCleaner.invoke(theUnsafe, buffer);
76          }
77      }
78  
79      private static final Cleaner INSTANCE = getCleaner();
80  
81      /**
82       * Releases memory held by the given {@link ByteBuffer}.
83       *
84       * @param buffer to release.
85       * @throws IllegalStateException on internal failure.
86       */
87      static void clean(final ByteBuffer buffer) {
88          try {
89              INSTANCE.clean(buffer);
90          } catch (final Exception e) {
91              throw new IllegalStateException("Failed to clean direct buffer.", e);
92          }
93      }
94  
95      private static Cleaner getCleaner() {
96          try {
97              return new Java8Cleaner();
98          } catch (final Exception e) {
99              try {
100                 return new Java9Cleaner();
101             } catch (final Exception e1) {
102                 throw new IllegalStateException("Failed to initialize a Cleaner.", e);
103             }
104         }
105     }
106 
107     /**
108      * Tests if were able to load a suitable cleaner for the current JVM. Attempting to call
109      * {@code ByteBufferCleaner#clean(ByteBuffer)} when this method returns false will result in an exception.
110      *
111      * @return {@code true} if cleaning is supported, {@code false} otherwise.
112      */
113     static boolean isSupported() {
114         return INSTANCE != null;
115     }
116 }