From f18c019287111ce622e6b8140f7061787e11b208 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Tue, 2 Mar 2021 18:08:26 +0000 Subject: [PATCH] 8247373: ArraysSupport.newLength doc, test, and exception message Reviewed-by: rriggs, psandoz, martin, prappo --- .../jdk/internal/util/ArraysSupport.java | 95 ++++++++++------ .../jdk/java/util/StringJoiner/MergeTest.java | 4 +- .../util/StringJoiner/StringJoinerTest.java | 4 +- .../util/ArraysSupport/NewLength.java | 102 ++++++++++++++++++ 4 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 test/jdk/jdk/internal/util/ArraysSupport/NewLength.java diff --git a/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java b/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java index 777905ea6a3..dbdc1833c76 100644 --- a/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java +++ b/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2021, 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 @@ -575,52 +575,83 @@ public class ArraysSupport { } /** - * The maximum length of array to allocate (unless necessary). - * Some VMs reserve some header words in an array. - * Attempts to allocate larger arrays may result in - * {@code OutOfMemoryError: Requested array size exceeds VM limit} + * A soft maximum array length imposed by array growth computations. + * Some JVMs (such as HotSpot) have an implementation limit that will cause + * + * OutOfMemoryError("Requested array size exceeds VM limit") + * + * to be thrown if a request is made to allocate an array of some length near + * Integer.MAX_VALUE, even if there is sufficient heap available. The actual + * limit might depend on some JVM implementation-specific characteristics such + * as the object header size. The soft maximum value is chosen conservatively so + * as to be smaller than any implementation limit that is likely to be encountered. */ - public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; + public static final int SOFT_MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8; /** - * Calculates a new array length given an array's current length, a preferred - * growth value, and a minimum growth value. If the preferred growth value - * is less than the minimum growth value, the minimum growth value is used in - * its place. If the sum of the current length and the preferred growth - * value does not exceed {@link #MAX_ARRAY_LENGTH}, that sum is returned. - * If the sum of the current length and the minimum growth value does not - * exceed {@code MAX_ARRAY_LENGTH}, then {@code MAX_ARRAY_LENGTH} is returned. - * If the sum does not overflow an int, then {@code Integer.MAX_VALUE} is - * returned. Otherwise, {@code OutOfMemoryError} is thrown. + * Computes a new array length given an array's current length, a minimum growth + * amount, and a preferred growth amount. The computation is done in an overflow-safe + * fashion. * - * @param oldLength current length of the array (must be non negative) - * @param minGrowth minimum required growth of the array length (must be - * positive) - * @param prefGrowth preferred growth of the array length (ignored, if less - * then {@code minGrowth}) - * @return the new length of the array - * @throws OutOfMemoryError if increasing {@code oldLength} by - * {@code minGrowth} overflows. + * This method is used by objects that contain an array that might need to be grown + * in order to fulfill some immediate need (the minimum growth amount) but would also + * like to request more space (the preferred growth amount) in order to accommodate + * potential future needs. The returned length is usually clamped at the soft maximum + * length in order to avoid hitting the JVM implementation limit. However, the soft + * maximum will be exceeded if the minimum growth amount requires it. + * + * If the preferred growth amount is less than the minimum growth amount, the + * minimum growth amount is used as the preferred growth amount. + * + * The preferred length is determined by adding the preferred growth amount to the + * current length. If the preferred length does not exceed the soft maximum length + * (SOFT_MAX_ARRAY_LENGTH) then the preferred length is returned. + * + * If the preferred length exceeds the soft maximum, we use the minimum growth + * amount. The minimum required length is determined by adding the minimum growth + * amount to the current length. If the minimum required length exceeds Integer.MAX_VALUE, + * then this method throws OutOfMemoryError. Otherwise, this method returns the greater of + * the soft maximum or the minimum required length. + * + * Note that this method does not do any array allocation itself; it only does array + * length growth computations. However, it will throw OutOfMemoryError as noted above. + * + * Note also that this method cannot detect the JVM's implementation limit, and it + * may compute and return a length value up to and including Integer.MAX_VALUE that + * might exceed the JVM's implementation limit. In that case, the caller will likely + * attempt an array allocation with that length and encounter an OutOfMemoryError. + * Of course, regardless of the length value returned from this method, the caller + * may encounter OutOfMemoryError if there is insufficient heap to fulfill the request. + * + * @param oldLength current length of the array (must be nonnegative) + * @param minGrowth minimum required growth amount (must be positive) + * @param prefGrowth preferred growth amount + * @return the new array length + * @throws OutOfMemoryError if the new length would exceed Integer.MAX_VALUE */ public static int newLength(int oldLength, int minGrowth, int prefGrowth) { + // preconditions not checked because of inlining // assert oldLength >= 0 // assert minGrowth > 0 - int newLength = Math.max(minGrowth, prefGrowth) + oldLength; - if (newLength - MAX_ARRAY_LENGTH <= 0) { - return newLength; + int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow + if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) { + return prefLength; + } else { + // put code cold in a separate method + return hugeLength(oldLength, minGrowth); } - return hugeLength(oldLength, minGrowth); } private static int hugeLength(int oldLength, int minGrowth) { int minLength = oldLength + minGrowth; if (minLength < 0) { // overflow - throw new OutOfMemoryError("Required array length too large"); + throw new OutOfMemoryError( + "Required array length " + oldLength + " + " + minGrowth + " is too large"); + } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) { + return SOFT_MAX_ARRAY_LENGTH; + } else { + return minLength; } - if (minLength <= MAX_ARRAY_LENGTH) { - return MAX_ARRAY_LENGTH; - } - return Integer.MAX_VALUE; } } diff --git a/test/jdk/java/util/StringJoiner/MergeTest.java b/test/jdk/java/util/StringJoiner/MergeTest.java index d83bbd01cb7..f47ed3ee094 100644 --- a/test/jdk/java/util/StringJoiner/MergeTest.java +++ b/test/jdk/java/util/StringJoiner/MergeTest.java @@ -33,7 +33,7 @@ import java.util.StringJoiner; import java.util.stream.Stream; import org.testng.annotations.Test; -import static jdk.internal.util.ArraysSupport.MAX_ARRAY_LENGTH; +import static jdk.internal.util.ArraysSupport.SOFT_MAX_ARRAY_LENGTH; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; @@ -172,7 +172,7 @@ public class MergeTest { } public void OOM() { - String maxString = "*".repeat(MAX_ARRAY_LENGTH); + String maxString = "*".repeat(SOFT_MAX_ARRAY_LENGTH); try { StringJoiner sj1 = new StringJoiner("", "", ""); diff --git a/test/jdk/java/util/StringJoiner/StringJoinerTest.java b/test/jdk/java/util/StringJoiner/StringJoinerTest.java index e587b4aa617..25948fb8e55 100644 --- a/test/jdk/java/util/StringJoiner/StringJoinerTest.java +++ b/test/jdk/java/util/StringJoiner/StringJoinerTest.java @@ -32,7 +32,7 @@ import java.util.ArrayList; import java.util.StringJoiner; import org.testng.annotations.Test; -import static jdk.internal.util.ArraysSupport.MAX_ARRAY_LENGTH; +import static jdk.internal.util.ArraysSupport.SOFT_MAX_ARRAY_LENGTH; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; @@ -49,7 +49,7 @@ public class StringJoinerTest { private static final String FOUR = "Four"; private static final String FIVE = "Five"; private static final String DASH = "-"; - private static final String MAX_STRING = "*".repeat(MAX_ARRAY_LENGTH); + private static final String MAX_STRING = "*".repeat(SOFT_MAX_ARRAY_LENGTH); public void addAddAll() { StringJoiner sj = new StringJoiner(DASH, "{", "}"); diff --git a/test/jdk/jdk/internal/util/ArraysSupport/NewLength.java b/test/jdk/jdk/internal/util/ArraysSupport/NewLength.java new file mode 100644 index 00000000000..91064a5afa1 --- /dev/null +++ b/test/jdk/jdk/internal/util/ArraysSupport/NewLength.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8247373 + * @modules java.base/jdk.internal.util + * @run testng NewLength + * @summary Test edge cases of ArraysSupport.newLength + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +import jdk.internal.util.ArraysSupport; + +public class NewLength { + static final int IMAX = Integer.MAX_VALUE; + static final int SOFT = ArraysSupport.SOFT_MAX_ARRAY_LENGTH; + + // Data that is expected to return a valid value. + + @DataProvider(name = "valid") + public Object[][] validProvider() { + return new Object[][] { + // old min pref expected + { 0, 1, 0, 1 }, + { 0, 1, 2, 2 }, + { 0, 2, 1, 2 }, + { 0, 1, SOFT-1, SOFT-1 }, + { 0, 1, SOFT, SOFT }, + { 0, 1, SOFT+1, SOFT }, + { 0, 1, IMAX, SOFT }, + { 0, SOFT-1, IMAX, SOFT }, + { 0, SOFT, IMAX, SOFT }, + { 0, SOFT+1, IMAX, SOFT+1 }, + { SOFT-2, 1, 2, SOFT }, + { SOFT-1, 1, 2, SOFT }, + { SOFT, 1, 2, SOFT+1 }, + { SOFT+1, 1, 2, SOFT+2 }, + { IMAX-2, 1, 2, IMAX-1 }, + { IMAX-1, 1, 2, IMAX }, + { SOFT-2, 1, IMAX, SOFT }, + { SOFT-1, 1, IMAX, SOFT }, + { SOFT, 1, IMAX, SOFT+1 }, + { SOFT+1, 1, IMAX, SOFT+2 }, + { IMAX-2, 1, IMAX, IMAX-1 }, + { IMAX-1, 1, IMAX, IMAX } + }; + } + + // Data that should provoke an OutOfMemoryError + + @DataProvider(name = "error") + public Object[][] errorProvider() { + return new Object[][] { + // old min pref + { 1, IMAX, IMAX }, + { SOFT, IMAX, 0 }, + { SOFT, IMAX, IMAX }, + { IMAX-1, 2, 0 }, + { IMAX, 1, 0 }, + { IMAX, IMAX, 0 }, + { IMAX, IMAX, IMAX } + }; + } + + @Test(dataProvider = "valid") + public void valid(int old, int min, int pref, int expected) { + assertEquals(ArraysSupport.newLength(old, min, pref), expected); + } + + @Test(dataProvider = "error") + public void error(int old, int min, int pref) { + try { + int r = ArraysSupport.newLength(old, min, pref); + fail("expected OutOfMemoryError, got normal return value of " + r); + } catch (OutOfMemoryError success) { } + } +}