Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6478546: FileInputStream.read() throws OutOfMemoryError when there is plenty available #8235

Closed
wants to merge 16 commits into from
Closed
45 changes: 35 additions & 10 deletions src/java.base/share/classes/java/io/FileInputStream.java
Expand Up @@ -25,11 +25,14 @@

package java.io;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import jdk.internal.misc.Blocker;
import jdk.internal.util.ArraysSupport;
import sun.nio.ch.DirectBuffer;
import sun.nio.ch.FileChannelImpl;
import sun.nio.ch.Util;

/**
* A {@code FileInputStream} obtains input bytes
Expand Down Expand Up @@ -243,13 +246,21 @@ public int read() throws IOException {
private native int read0() throws IOException;

/**
* Reads a subarray as a sequence of bytes.
* @param b the data to be written
* Reads a subarray as a sequence of bytes via a temporary direct
* buffer.
*
* @param b the data to be read
* @param off the start offset in the data
* @param len the number of bytes that are written
* @param len the number of bytes to be read
* @param bufAddr the address of the temporary direct buffer's array
* @param bufSize the size of the temporary direct buffer's array
* @return the total number of bytes read into the buffer, or -1
* if there is no more data because the end of the stream
* has been reached.
* @throws IOException If an I/O error has occurred.
*/
private native int readBytes(byte[] b, int off, int len) throws IOException;
private native int readBytes0(byte[] b, int off, int len, long bufAddr,
int bufSize) throws IOException;

/**
* Reads up to {@code b.length} bytes of data from this input
Expand All @@ -264,11 +275,18 @@ public int read() throws IOException {
*/
@Override
public int read(byte[] b) throws IOException {
long comp = Blocker.begin();
int bufSize = RandomAccessFile.bufferSize(b.length);
ByteBuffer buf = Util.getTemporaryDirectBuffer(bufSize);
try {
return readBytes(b, 0, b.length);
long comp = Blocker.begin();
try {
long bufAddr = ((DirectBuffer)buf).address();
return readBytes0(b, 0, b.length, bufAddr, bufSize);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

Expand All @@ -288,11 +306,18 @@ public int read(byte[] b) throws IOException {
*/
@Override
public int read(byte[] b, int off, int len) throws IOException {
long comp = Blocker.begin();
int size = RandomAccessFile.bufferSize(len);
ByteBuffer buf = Util.getTemporaryDirectBuffer(size);
try {
return readBytes(b, off, len);
long comp = Blocker.begin();
try {
long address = ((DirectBuffer)buf).address();
return readBytes0(b, off, len, address, size);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

Expand Down
46 changes: 32 additions & 14 deletions src/java.base/share/classes/java/io/FileOutputStream.java
Expand Up @@ -25,12 +25,14 @@

package java.io;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import jdk.internal.access.SharedSecrets;
import jdk.internal.access.JavaIOFileDescriptorAccess;
import jdk.internal.misc.Blocker;
import sun.nio.ch.DirectBuffer;
import sun.nio.ch.FileChannelImpl;

import sun.nio.ch.Util;

/**
* A file output stream is an output stream for writing data to a
Expand Down Expand Up @@ -326,15 +328,17 @@ public void write(int b) throws IOException {
}

/**
* Writes a sub array as a sequence of bytes.
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes that are written
* @param append {@code true} to first advance the position to the
* end of file
* Writes a subarray as a sequence of bytes via a temporary direct buffer.
*
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes to be written
* @param bufAddr the address of the temporary direct buffer's array
* @param bufSize the size of the temporary direct buffer's array
* @throws IOException If an I/O error has occurred.
*/
private native void writeBytes(byte[] b, int off, int len, boolean append)
private native void writeBytes0(byte[] b, int off, int len, boolean append,
long bufAddr, int bufSize)
throws IOException;

/**
Expand All @@ -347,11 +351,18 @@ private native void writeBytes(byte[] b, int off, int len, boolean append)
@Override
public void write(byte[] b) throws IOException {
boolean append = fdAccess.getAppend(fd);
long comp = Blocker.begin();
int bufSize = RandomAccessFile.bufferSize(b.length);
ByteBuffer buf = Util.getTemporaryDirectBuffer(bufSize);
try {
writeBytes(b, 0, b.length, append);
long comp = Blocker.begin();
try {
long bufAddr = ((DirectBuffer)buf).address();
writeBytes0(b, 0, b.length, append, bufAddr, bufSize);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

Expand All @@ -367,11 +378,18 @@ public void write(byte[] b) throws IOException {
@Override
public void write(byte[] b, int off, int len) throws IOException {
boolean append = fdAccess.getAppend(fd);
long comp = Blocker.begin();
int size = RandomAccessFile.bufferSize(len);
ByteBuffer buf = Util.getTemporaryDirectBuffer(size);
try {
writeBytes(b, off, len, append);
long comp = Blocker.begin();
try {
long address = ((DirectBuffer)buf).address();
writeBytes0(b, off, len, append, address, size);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

Expand Down
94 changes: 84 additions & 10 deletions src/java.base/share/classes/java/io/RandomAccessFile.java
Expand Up @@ -25,13 +25,15 @@

package java.io;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import jdk.internal.access.JavaIORandomAccessFileAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.Blocker;
import sun.nio.ch.DirectBuffer;
import sun.nio.ch.FileChannelImpl;

import sun.nio.ch.Util;

/**
* Instances of this class support both reading and writing to a
Expand Down Expand Up @@ -61,7 +63,13 @@

public class RandomAccessFile implements DataOutput, DataInput, Closeable {

private FileDescriptor fd;
// minimum capacity of temporary direct buffers
private static final int MIN_BUFFER_SIZE = 8192;

// maximim capacity of temporary direct buffers
private static final int MAX_BUFFER_SIZE = 65536;

private final FileDescriptor fd;
private volatile FileChannel channel;
private boolean rw;

Expand All @@ -81,6 +89,31 @@ public class RandomAccessFile implements DataOutput, DataInput, Closeable {
private static final int O_DSYNC = 8;
private static final int O_TEMPORARY = 16;

/**
* Calculate the size of a temporary direct buffer.
*
* @param the number of bytes in question
* @return the size of buffer to use
*/
// package scope
static final int bufferSize(int len) {
if (len <= 0) {
return 0;
}

if (len >= MAX_BUFFER_SIZE) {
return MAX_BUFFER_SIZE;
}

if (len % MIN_BUFFER_SIZE == 0) {
return len;
}

// len is positive and not a multiple of MIN_BUFFER_SIZE; return
// the smallest multiple of MIN_BUFFER_SIZE greater than len.
return (1 + len/MIN_BUFFER_SIZE)*MIN_BUFFER_SIZE;
}

/**
* Creates a random access file stream to read from, and optionally
* to write to, a file with the specified name. A new
Expand Down Expand Up @@ -385,15 +418,37 @@ public int read() throws IOException {
* @throws IOException If an I/O error has occurred.
*/
private int readBytes(byte[] b, int off, int len) throws IOException {
long comp = Blocker.begin();
int bufSize = bufferSize(len);
ByteBuffer buf = Util.getTemporaryDirectBuffer(bufSize);
try {
return readBytes0(b, off, len);
long comp = Blocker.begin();
try {
long bufAddr = ((DirectBuffer)buf).address();
return readBytes0(b, off, len, bufAddr, bufSize);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

private native int readBytes0(byte[] b, int off, int len) throws IOException;
/**
* Reads a subarray as a sequence of bytes via a temporary direct
* buffer.
*
* @param b the data to be read
* @param off the start offset in the data
* @param len the number of bytes to be read
* @param bufAddr the address of the temporary direct buffer's array
* @param bufSize the size of the temporary direct buffer's array
* @return the total number of bytes read into the buffer, or -1
* if there is no more data because the end of the stream
* has been reached.
* @throws IOException If an I/O error has occurred.
*/
private native int readBytes0(byte[] b, int off, int len, long bufAddr,
int bufSize) throws IOException;

/**
* Reads up to {@code len} bytes of data from this file into an
Expand Down Expand Up @@ -557,15 +612,34 @@ public void write(int b) throws IOException {
* @throws IOException If an I/O error has occurred.
*/
private void writeBytes(byte[] b, int off, int len) throws IOException {
long comp = Blocker.begin();
int bufSize = bufferSize(len);
ByteBuffer buf = Util.getTemporaryDirectBuffer(bufSize);
try {
writeBytes0(b, off, len);
long comp = Blocker.begin();
try {
long bufAddr = ((DirectBuffer)buf).address();
writeBytes0(b, off, len, bufAddr, bufSize);
} finally {
Blocker.end(comp);
}
} finally {
Blocker.end(comp);
Util.releaseTemporaryDirectBuffer(buf);
}
}

private native void writeBytes0(byte[] b, int off, int len) throws IOException;
/**
* Writes a subarray as a sequence of bytes via a temporary direct buffer.
*
* @param b the data to be written
* @param off the start offset in the data
* @param len the number of bytes to be written
* @param bufAddr the address of the temporary direct buffer's array
* @param bufSize the size of the temporary direct buffer's array
* @throws IOException If an I/O error has occurred.
*/
private native void writeBytes0(byte[] b, int off, int len,
long bufAddr, int bufSize)
throws IOException;

/**
* Writes {@code b.length} bytes from the specified byte array
Expand Down
8 changes: 4 additions & 4 deletions src/java.base/share/native/libjava/FileInputStream.c
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -67,9 +67,9 @@ Java_java_io_FileInputStream_read0(JNIEnv *env, jobject this) {
}

JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len) {
return readBytes(env, this, bytes, off, len, fis_fd);
Java_java_io_FileInputStream_readBytes0(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len, jlong bufAddr, jint bufSize) {
return readBytes(env, this, bytes, off, len, bufAddr, bufSize, fis_fd);
}

JNIEXPORT jlong JNICALL
Expand Down
9 changes: 5 additions & 4 deletions src/java.base/share/native/libjava/FileOutputStream.c
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2997, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -65,8 +65,9 @@ Java_java_io_FileOutputStream_write(JNIEnv *env, jobject this, jint byte, jboole
}

JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_writeBytes(JNIEnv *env,
jobject this, jbyteArray bytes, jint off, jint len, jboolean append) {
writeBytes(env, this, bytes, off, len, append, fos_fd);
Java_java_io_FileOutputStream_writeBytes0(JNIEnv *env,
jobject this, jbyteArray bytes, jint off, jint len, jboolean append,
jlong bufAddr, jint bufSize) {
writeBytes(env, this, bytes, off, len, append, bufAddr, bufSize, fos_fd);
}

10 changes: 6 additions & 4 deletions src/java.base/share/native/libjava/RandomAccessFile.c
Expand Up @@ -74,8 +74,9 @@ Java_java_io_RandomAccessFile_read0(JNIEnv *env, jobject this) {

JNIEXPORT jint JNICALL
Java_java_io_RandomAccessFile_readBytes0(JNIEnv *env,
jobject this, jbyteArray bytes, jint off, jint len) {
return readBytes(env, this, bytes, off, len, raf_fd);
jobject this, jbyteArray bytes, jint off, jint len,
jlong bufAddr, jint bufSize) {
return readBytes(env, this, bytes, off, len, bufAddr, bufSize, raf_fd);
}

JNIEXPORT void JNICALL
Expand All @@ -85,8 +86,9 @@ Java_java_io_RandomAccessFile_write0(JNIEnv *env, jobject this, jint byte) {

JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_writeBytes0(JNIEnv *env,
jobject this, jbyteArray bytes, jint off, jint len) {
writeBytes(env, this, bytes, off, len, JNI_FALSE, raf_fd);
jobject this, jbyteArray bytes, jint off, jint len,
jlong bufAddr, jint bufSize) {
writeBytes(env, this, bytes, off, len, JNI_FALSE, bufAddr, bufSize, raf_fd);
}

JNIEXPORT jlong JNICALL
Expand Down