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 * https://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 * <strong>Warning:</strong> 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 }