Skip to content

Commit 60b61da

Browse files
author
Gary Gregory
committed
[IO-726] Add MemoryMappedInputStream, #215.
Modified PR 215 from shollander with: - Do not write the console. - Simplify cleaner check support. - Simplify exception handling. - Don't initialize instance variables to their default values. - Un-nest else clause. - Declaring interfaces as static is redundant. - Use EOF constant instead of magic number. - Don't throw RuntimeException, use IllegalStateException. - Pacakge private class does not need public method. - Use final. - Improve Javadocs. - Javadoc sentences should end in a period. - Javadoc do not need first sentence to be in an HTML p element. - No need to use `this.` when you do not need to. - In-line some single use variables. - Sort members.
1 parent eab7b6b commit 60b61da

6 files changed

Lines changed: 602 additions & 73 deletions

File tree

src/changes/changes.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ The <action> type attribute can be add,update,fix,remove.
7474
<action dev="ggregory" type="fix" due-to="Arturo Bernal">
7575
Fix Javadoc in ThreadMonitor#run() method. #273.
7676
</action>
77+
<action issue="IO-726" dev="ggregory" type="fix" due-to="shollander, Gary Gregory">
78+
Add MemoryMappedInputStream. #215.
79+
</action>
7780
<!-- ADD -->
7881
<action dev="ggregory" type="add" due-to="Gary Gregory">
7982
Add BrokenReader.INSTANCE.

src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java

Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.io.InputStream;
21-
import java.lang.reflect.Field;
22-
import java.lang.reflect.Method;
2321
import java.nio.ByteBuffer;
2422
import java.nio.channels.FileChannel;
2523
import java.nio.file.Path;
@@ -40,29 +38,12 @@
4038
*
4139
* @since 2.9.0
4240
*/
43-
@SuppressWarnings("restriction")
4441
public final class BufferedFileChannelInputStream extends InputStream {
4542

4643
private final ByteBuffer byteBuffer;
4744

4845
private final FileChannel fileChannel;
4946

50-
private static final Class<?> DIRECT_BUFFER_CLASS = getDirectBufferClass();
51-
52-
private static Class<?> getDirectBufferClass() {
53-
Class<?> res = null;
54-
try {
55-
res = Class.forName("sun.nio.ch.DirectBuffer");
56-
} catch (final IllegalAccessError | ClassNotFoundException ignored) {
57-
// ignored
58-
}
59-
return res;
60-
}
61-
62-
private static boolean isDirectBuffer(final Object object) {
63-
return DIRECT_BUFFER_CLASS != null && DIRECT_BUFFER_CLASS.isInstance(object);
64-
}
65-
6647
/**
6748
* Constructs a new instance for the given File.
6849
*
@@ -115,75 +96,30 @@ public synchronized int available() throws IOException {
11596

11697
/**
11798
* Attempts to clean up a ByteBuffer if it is direct or memory-mapped. This uses an *unsafe* Sun API that will cause
118-
* errors if one attempts to read from the disposed buffer. However, neither the bytes allocated to direct buffers
119-
* nor file descriptors opened for memory-mapped buffers put pressure on the garbage collector. Waiting for garbage
99+
* errors if one attempts to read from the disposed buffer. However, neither the bytes allocated to direct buffers nor
100+
* file descriptors opened for memory-mapped buffers put pressure on the garbage collector. Waiting for garbage
120101
* collection may lead to the depletion of off-heap memory or huge numbers of open files. There's unfortunately no
121102
* standard API to manually dispose of these kinds of buffers.
122103
*
123104
* @param buffer the buffer to clean.
124105
*/
125106
private void clean(final ByteBuffer buffer) {
126-
if (isDirectBuffer(buffer)) {
107+
if (buffer.isDirect()) {
127108
cleanDirectBuffer(buffer);
128109
}
129110
}
130111

131112
/**
132-
* In Java 8, the type of DirectBuffer.cleaner() was sun.misc.Cleaner, and it was possible to access the method
133-
* sun.misc.Cleaner.clean() to invoke it. The type changed to jdk.internal.ref.Cleaner in later JDKs, and the
134-
* .clean() method is not accessible even with reflection. However sun.misc.Unsafe added a invokeCleaner() method in
135-
* JDK 9+ and this is still accessible with reflection.
113+
* In Java 8, the type of {@code sun.nio.ch.DirectBuffer.cleaner()} was {@code sun.misc.Cleaner}, and it was possible to
114+
* access the method {@code sun.misc.Cleaner.clean()} to invoke it. The type changed to {@code jdk.internal.ref.Cleaner}
115+
* in later JDKs, and the {@code clean()} method is not accessible even with reflection. However {@code sun.misc.Unsafe}
116+
* added an {@code invokeCleaner()} method in JDK 9+ and this is still accessible with reflection.
136117
*
137118
* @param buffer the buffer to clean. must be a DirectBuffer.
138119
*/
139120
private void cleanDirectBuffer(final ByteBuffer buffer) {
140-
//
141-
// Ported from StorageUtils.scala.
142-
//
143-
// private val bufferCleaner: DirectBuffer => Unit =
144-
// if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9)) {
145-
// val cleanerMethod =
146-
// Utils.classForName("sun.misc.Unsafe").getMethod("invokeCleaner", classOf[ByteBuffer])
147-
// val unsafeField = classOf[Unsafe].getDeclaredField("theUnsafe")
148-
// unsafeField.setAccessible(true)
149-
// val unsafe = unsafeField.get(null).asInstanceOf[Unsafe]
150-
// buffer: DirectBuffer => cleanerMethod.invoke(unsafe, buffer)
151-
// } else {
152-
// val cleanerMethod = Utils.classForName("sun.misc.Cleaner").getMethod("clean")
153-
// buffer: DirectBuffer => {
154-
// // Careful to avoid the return type of .cleaner(), which changes with JDK
155-
// val cleaner: AnyRef = buffer.cleaner()
156-
// if (cleaner != null) {
157-
// cleanerMethod.invoke(cleaner)
158-
// }
159-
// }
160-
// }
161-
//
162-
final String specVer = System.getProperty("java.specification.version");
163-
if ("1.8".equals(specVer)) {
164-
// On Java 8, but also compiles on Java 11.
165-
try {
166-
final Class<?> clsCleaner = Class.forName("sun.misc.Cleaner");
167-
final Method cleanerMethod = DIRECT_BUFFER_CLASS.getMethod("cleaner");
168-
final Object cleaner = cleanerMethod.invoke(buffer);
169-
if (cleaner != null) {
170-
final Method cleanMethod = clsCleaner.getMethod("clean");
171-
cleanMethod.invoke(cleaner);
172-
}
173-
} catch (final ReflectiveOperationException e) {
174-
throw new IllegalStateException(e);
175-
}
176-
} else {
177-
// On Java 9 and up, but compiles on Java 8.
178-
try {
179-
final Class<?> clsUnsafe = Class.forName("sun.misc.Unsafe");
180-
final Method cleanerMethod = clsUnsafe.getMethod("invokeCleaner", ByteBuffer.class);
181-
final Field unsafeField = clsUnsafe.getDeclaredField("theUnsafe");
182-
unsafeField.setAccessible(true);
183-
cleanerMethod.invoke(unsafeField.get(null), buffer);
184-
} catch (final ReflectiveOperationException e) {
185-
throw new IllegalStateException(e);
186-
}
121+
if (ByteBufferCleaner.isSupported()) {
122+
ByteBufferCleaner.clean(buffer);
187123
}
188124
}
189125

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 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+
class ByteBufferCleaner {
36+
37+
private interface Cleaner {
38+
void clean(ByteBuffer buffer) throws ReflectiveOperationException;
39+
}
40+
41+
private static 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 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+
}

0 commit comments

Comments
 (0)