Skip to content

Commit d5eccce

Browse files
authored
Clear direct byte buffers (#855)
* Add Buffers clear() methods * BufferedFileChannelInputStream now clears its direct memory byte buffer on close. MemoryMappedFileInputStream now clears its direct memory byte buffer on close. FileChannels.contentEquals(ReadableByteChannel, ReadableByteChannel, int) now clears its direct memory byte buffer on close. BufferedFileChannelInputStream now clears its direct memory byte buffer on close. MemoryMappedFileInputStream now clears its direct memory byte buffer on close.
1 parent fecdad4 commit d5eccce

4 files changed

Lines changed: 48 additions & 36 deletions

File tree

src/main/java/org/apache/commons/io/channels/FileChannels.java

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.channels.SeekableByteChannel;
2525
import java.util.Objects;
2626

27+
import org.apache.commons.io.Buffers;
2728
import org.apache.commons.io.IOUtils;
2829

2930
/**
@@ -67,33 +68,38 @@ public static boolean contentEquals(final ReadableByteChannel channel1, final Re
6768
// Don't use ByteBuffer#compact() to avoid extra copying.
6869
final ByteBuffer c1Buffer = ByteBuffer.allocateDirect(bufferCapacity);
6970
final ByteBuffer c2Buffer = ByteBuffer.allocateDirect(bufferCapacity);
70-
int c1NumRead = 0;
71-
int c2NumRead = 0;
72-
boolean c1Read0 = false;
73-
boolean c2Read0 = false;
74-
// If a channel is a non-blocking channel, it may return 0 bytes read for any given call.
75-
while (true) {
76-
if (!c2Read0) {
77-
c1NumRead = readToLimit(channel1, c1Buffer);
78-
c1Buffer.clear();
79-
c1Read0 = c1NumRead == 0;
80-
}
81-
if (!c1Read0) {
82-
c2NumRead = readToLimit(channel2, c2Buffer);
83-
c2Buffer.clear();
84-
c2Read0 = c2NumRead == 0;
85-
}
86-
if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) {
87-
return c1Buffer.equals(c2Buffer);
88-
}
89-
if (c1NumRead == 0 || c2NumRead == 0) {
90-
// 0 may be returned from a non-blocking channel.
91-
Thread.yield();
92-
continue;
93-
}
94-
if (c1NumRead != c2NumRead || !c1Buffer.equals(c2Buffer)) {
95-
return false;
71+
try {
72+
int c1NumRead = 0;
73+
int c2NumRead = 0;
74+
boolean c1Read0 = false;
75+
boolean c2Read0 = false;
76+
// If a channel is a non-blocking channel, it may return 0 bytes read for any given call.
77+
while (true) {
78+
if (!c2Read0) {
79+
c1NumRead = readToLimit(channel1, c1Buffer);
80+
c1Buffer.clear();
81+
c1Read0 = c1NumRead == 0;
82+
}
83+
if (!c1Read0) {
84+
c2NumRead = readToLimit(channel2, c2Buffer);
85+
c2Buffer.clear();
86+
c2Read0 = c2NumRead == 0;
87+
}
88+
if (c1NumRead == IOUtils.EOF && c2NumRead == IOUtils.EOF) {
89+
return c1Buffer.equals(c2Buffer);
90+
}
91+
if (c1NumRead == 0 || c2NumRead == 0) {
92+
// 0 may be returned from a non-blocking channel.
93+
Thread.yield();
94+
continue;
95+
}
96+
if (c1NumRead != c2NumRead || !c1Buffer.equals(c2Buffer)) {
97+
return false;
98+
}
9699
}
100+
} finally {
101+
Buffers.clear(c1Buffer);
102+
Buffers.clear(c2Buffer);
97103
}
98104
}
99105

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public synchronized int available() throws IOException {
220220
* @param buffer the buffer to clean.
221221
*/
222222
private void clean(final ByteBuffer buffer) {
223-
if (!clean && buffer.isDirect() && ByteBufferCleaner.isSupported()) {
223+
if (!clean && ByteBufferCleaner.isSupported()) {
224224
ByteBufferCleaner.clean(buffer);
225225
clean = true;
226226
}

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
1718
package org.apache.commons.io.input;
1819

1920
import java.lang.reflect.Field;
2021
import java.lang.reflect.Method;
2122
import java.nio.ByteBuffer;
2223

24+
import org.apache.commons.io.Buffers;
25+
2326
/**
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+
* Cleans a direct {@link ByteBuffer}. Without manual intervention, direct ByteBuffers will be cleaned eventually upon garbage collection. However, this should
28+
* not be relied upon since it may not occur in a timely fashion - especially since off heap ByeBuffers don't put pressure on the garbage collector.
2729
* <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+
* <strong>Warning:</strong> Do not attempt to use a direct {@link ByteBuffer} that has been cleaned or bad things will happen. Don't use this class unless you
31+
* can ensure that the cleaned buffer will not be accessed anymore.
3032
* </p>
3133
* <p>
3234
* See <a href=https://bugs.openjdk.java.net/browse/JDK-4724038>JDK-4724038</a>
@@ -35,6 +37,7 @@
3537
final class ByteBufferCleaner {
3638

3739
private interface Cleaner {
40+
3841
void clean(ByteBuffer buffer) throws ReflectiveOperationException;
3942
}
4043

@@ -86,7 +89,10 @@ public void clean(final ByteBuffer buffer) throws ReflectiveOperationException {
8689
*/
8790
static void clean(final ByteBuffer buffer) {
8891
try {
89-
INSTANCE.clean(buffer);
92+
if (buffer.isDirect()) {
93+
Buffers.clearWritable(buffer);
94+
INSTANCE.clean(buffer);
95+
}
9096
} catch (final Exception e) {
9197
throw new IllegalStateException("Failed to clean direct buffer.", e);
9298
}
@@ -105,8 +111,8 @@ private static Cleaner getCleaner() {
105111
}
106112

107113
/**
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.
114+
* Tests if were able to load a suitable cleaner for the current JVM. Attempting to call {@code ByteBufferCleaner#clean(ByteBuffer)} when this method
115+
* returns false will result in an exception.
110116
*
111117
* @return {@code true} if cleaning is supported, {@code false} otherwise.
112118
*/

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public int available() throws IOException {
172172
}
173173

174174
private void cleanBuffer() {
175-
if (ByteBufferCleaner.isSupported() && buffer.isDirect()) {
175+
if (ByteBufferCleaner.isSupported()) {
176176
ByteBufferCleaner.clean(buffer);
177177
}
178178
}

0 commit comments

Comments
 (0)