From e52a2aeeacaeb26c801b6e31f8e67e61b1ea2de3 Mon Sep 17 00:00:00 2001 From: Sergey Tsypanov Date: Thu, 6 Apr 2023 11:17:21 +0000 Subject: [PATCH] 8304745: Lazily initialize byte[] in java.io.BufferedInputStream Reviewed-by: liach, bpb, jpai --- .../classes/java/io/BufferedInputStream.java | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/io/BufferedInputStream.java b/src/java.base/share/classes/java/io/BufferedInputStream.java index 8a7bdfad966..e5a44761b8a 100644 --- a/src/java.base/share/classes/java/io/BufferedInputStream.java +++ b/src/java.base/share/classes/java/io/BufferedInputStream.java @@ -57,6 +57,8 @@ public class BufferedInputStream extends FilterInputStream { private static final int DEFAULT_BUFFER_SIZE = 8192; + private static final byte[] EMPTY = new byte[0]; + /** * As this class is used early during bootstrap, it's motivated to use * Unsafe.compareAndSetObject instead of AtomicReferenceFieldUpdater @@ -70,6 +72,9 @@ public class BufferedInputStream extends FilterInputStream { // initialized to null when BufferedInputStream is sub-classed private final InternalLock lock; + // initial buffer size (DEFAULT_BUFFER_SIZE or size specified to constructor) + private final int initialSize; + /** * The internal buffer array where the data is stored. When necessary, * it may be replaced by another array of @@ -166,14 +171,40 @@ public class BufferedInputStream extends FilterInputStream { } /** - * Check to make sure that buffer has not been nulled out due to - * close; if not return it; + * Returns the internal buffer, optionally allocating it if empty. + * @param allocateIfEmpty true to allocate if empty + * @throws IOException if the stream is closed (buf is null) + */ + private byte[] getBufIfOpen(boolean allocateIfEmpty) throws IOException { + byte[] buffer = buf; + if (allocateIfEmpty && buffer == EMPTY) { + buffer = new byte[initialSize]; + if (!U.compareAndSetReference(this, BUF_OFFSET, EMPTY, buffer)) { + // re-read buf + buffer = buf; + } + } + if (buffer == null) { + throw new IOException("Stream closed"); + } + return buffer; + } + + /** + * Returns the internal buffer, allocating it if empty. + * @throws IOException if the stream is closed (buf is null) */ private byte[] getBufIfOpen() throws IOException { - byte[] buffer = buf; - if (buffer == null) + return getBufIfOpen(true); + } + + /** + * Throws IOException if the stream is closed (buf is null). + */ + private void ensureOpen() throws IOException { + if (buf == null) { throw new IOException("Stream closed"); - return buffer; + } } /** @@ -205,13 +236,15 @@ public class BufferedInputStream extends FilterInputStream { if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } - buf = new byte[size]; - - // use monitors when BufferedInputStream is sub-classed + initialSize = size; if (getClass() == BufferedInputStream.class) { + // use internal lock and lazily create buffer when not subclassed lock = InternalLock.newLockOrNull(); + buf = EMPTY; } else { + // use monitors and eagerly create buffer when subclassed lock = null; + buf = new byte[size]; } } @@ -307,7 +340,8 @@ public class BufferedInputStream extends FilterInputStream { if there is no mark/reset activity, do not bother to copy the bytes into the local buffer. In this way buffered streams will cascade harmlessly. */ - if (len >= getBufIfOpen().length && markpos == -1) { + int size = Math.max(getBufIfOpen(false).length, initialSize); + if (len >= size && markpos == -1) { return getInIfOpen().read(b, off, len); } fill(); @@ -374,7 +408,7 @@ public class BufferedInputStream extends FilterInputStream { } private int implRead(byte[] b, int off, int len) throws IOException { - getBufIfOpen(); // Check for closed stream + ensureOpen(); if ((off | len | (off + len) | (b.length - (off + len))) < 0) { throw new IndexOutOfBoundsException(); } else if (len == 0) { @@ -421,7 +455,7 @@ public class BufferedInputStream extends FilterInputStream { } private long implSkip(long n) throws IOException { - getBufIfOpen(); // Check for closed stream + ensureOpen(); if (n <= 0) { return 0; } @@ -544,7 +578,7 @@ public class BufferedInputStream extends FilterInputStream { } private void implReset() throws IOException { - getBufIfOpen(); // Cause exception if closed + ensureOpen(); if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos;