8247373: ArraysSupport.newLength doc, test, and exception message

Reviewed-by: rriggs, psandoz, martin, prappo
This commit is contained in:
Stuart Marks 2021-03-02 18:08:26 +00:00
parent 96c43210d3
commit f18c019287
4 changed files with 169 additions and 36 deletions

View File

@ -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;
}
}

View File

@ -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("", "", "");

View File

@ -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, "{", "}");

View File

@ -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) { }
}
}