ByteBufferCleaner.java

  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. import java.lang.reflect.Field;
  19. import java.lang.reflect.Method;
  20. import java.nio.ByteBuffer;

  21. /**
  22.  * Cleans a direct {@link ByteBuffer}. Without manual intervention, direct ByteBuffers will be cleaned eventually upon
  23.  * garbage collection. However, this should not be relied upon since it may not occur in a timely fashion -
  24.  * especially since off heap ByeBuffers don't put pressure on the garbage collector.
  25.  * <p>
  26.  * <strong>Warning:</strong> Do not attempt to use a direct {@link ByteBuffer} that has been cleaned or bad things will happen.
  27.  * Don't use this class unless you can ensure that the cleaned buffer will not be accessed anymore.
  28.  * </p>
  29.  * <p>
  30.  * See <a href=https://bugs.openjdk.java.net/browse/JDK-4724038>JDK-4724038</a>
  31.  * </p>
  32.  */
  33. final class ByteBufferCleaner {

  34.     private interface Cleaner {
  35.         void clean(ByteBuffer buffer) throws ReflectiveOperationException;
  36.     }

  37.     private static final class Java8Cleaner implements Cleaner {

  38.         private final Method cleanerMethod;
  39.         private final Method cleanMethod;

  40.         private Java8Cleaner() throws ReflectiveOperationException, SecurityException {
  41.             cleanMethod = Class.forName("sun.misc.Cleaner").getMethod("clean");
  42.             cleanerMethod = Class.forName("sun.nio.ch.DirectBuffer").getMethod("cleaner");
  43.         }

  44.         @Override
  45.         public void clean(final ByteBuffer buffer) throws ReflectiveOperationException {
  46.             final Object cleaner = cleanerMethod.invoke(buffer);
  47.             if (cleaner != null) {
  48.                 cleanMethod.invoke(cleaner);
  49.             }
  50.         }
  51.     }

  52.     private static final class Java9Cleaner implements Cleaner {

  53.         private final Object theUnsafe;
  54.         private final Method invokeCleaner;

  55.         private Java9Cleaner() throws ReflectiveOperationException, SecurityException {
  56.             final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
  57.             final Field field = unsafeClass.getDeclaredField("theUnsafe");
  58.             field.setAccessible(true);
  59.             theUnsafe = field.get(null);
  60.             invokeCleaner = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
  61.         }

  62.         @Override
  63.         public void clean(final ByteBuffer buffer) throws ReflectiveOperationException {
  64.             invokeCleaner.invoke(theUnsafe, buffer);
  65.         }
  66.     }

  67.     private static final Cleaner INSTANCE = getCleaner();

  68.     /**
  69.      * Releases memory held by the given {@link ByteBuffer}.
  70.      *
  71.      * @param buffer to release.
  72.      * @throws IllegalStateException on internal failure.
  73.      */
  74.     static void clean(final ByteBuffer buffer) {
  75.         try {
  76.             INSTANCE.clean(buffer);
  77.         } catch (final Exception e) {
  78.             throw new IllegalStateException("Failed to clean direct buffer.", e);
  79.         }
  80.     }

  81.     private static Cleaner getCleaner() {
  82.         try {
  83.             return new Java8Cleaner();
  84.         } catch (final Exception e) {
  85.             try {
  86.                 return new Java9Cleaner();
  87.             } catch (final Exception e1) {
  88.                 throw new IllegalStateException("Failed to initialize a Cleaner.", e);
  89.             }
  90.         }
  91.     }

  92.     /**
  93.      * Tests if were able to load a suitable cleaner for the current JVM. Attempting to call
  94.      * {@code ByteBufferCleaner#clean(ByteBuffer)} when this method returns false will result in an exception.
  95.      *
  96.      * @return {@code true} if cleaning is supported, {@code false} otherwise.
  97.      */
  98.     static boolean isSupported() {
  99.         return INSTANCE != null;
  100.     }
  101. }