The Java "Requested array size exceeds VM limit" error message
The other day I saw this tweet exchange between Lorin Hochstein, Stuart Marks and Peter Lawrey. I follow the last two of them on Twitter.
The tweet was about the "Requested array size exceeds VM limit" OutOfMemoryError
message.
I have written about it in this previous post.
But I feel it was kind of lost in all of the noise about the particulars of writing a List
implementation.
Therefore, in this blog post, I will discuss exclusively about the mentioned error message.
Let's begin.
Theoretical maximum array length
To create a new, single dimension, array instance you use a array creation expression like so:
var a = new Object[123];
The value inside the brackets, i.e. the length of the new array, must be convertible to an int
type.
Therefore, the following does not compile:
// does not compile!
long length = 123L;
var a = new Object[length];
As a long
value cannot be converted, at least not without an explicit cast, to an int
value.
Additionally, the value inside the brackets cannot be negative. For example, the following statement:
var a = new Object[-1];
Fails at runtime with a java.lang.NegativeArraySizeException
:
Exception in thread "main" java.lang.NegativeArraySizeException: -1
at post.InvalidArrayCreation.main(InvalidArrayCreation.java:11)
To sum up, the rules are:
-
value (or expression) must be of type
int
or must be of type that can be promoted to anint
(like abyte
orshort
for example); and -
it must be non-negative.
Therefore, and in theory, the maximum array length would be given by the Integer.MAX_VALUE
constant:
assertEquals(Integer.MAX_VALUE, 2_147_483_647);
Let's try to instantiate an array using the theoretical maximum value. The following program does just that:
public class VMLimit {
public static void main(String[] args) {
var arr = new Object[Integer.MAX_VALUE];
System.out.format("Actual array length is %,d%n", arr.length);
}
}
When executed the program fails with an OutOfMemoryError
.
The message is the one that is the subject of this post:
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at post.VMLimit.main(VMLimit.java:20)
Our theoretical maximum length is obviously wrong. It hits a VM limit of some sorts. So the question becomes:
-
what is the largest array that we can create?
Actual maximum array length
From the error message we know the limit is imposed by the JVM somehow. So, in order to find out the actual maximum allowed array length, let's explore the JDK source code.
The array length validation code
If we grep
for the "Requested array size..." message we can find the following method in the hotspot/share/oops/klass.cpp file:
void Klass::check_array_allocation_length(int length, int max_length, TRAPS) {
if (length > max_length) {
if (!THREAD->in_retryable_allocation()) {
report_java_out_of_memory("Requested array size exceeds VM limit");
JvmtiExport::post_array_size_exhausted();
THROW_OOP(Universe::out_of_memory_error_array_size());
} else {
THROW_OOP(Universe::out_of_memory_error_retry());
}
} else if (length < 0) {
THROW_MSG(vmSymbols::java_lang_NegativeArraySizeException(), err_msg("%d", length));
}
}
I don't know C++. But this section is quite readable for a Java programmer.
It is a code that validates the array length prior to the actual object allocation. Or, at least, it is safe to assume it is.
Let's start from the bottom.
If the length is negative, a java.lang.NegativeArraySizeException
is thrown.
This matches the new Object[-1]
example from the previous section.
If length is greater than max_length
then "our message" is set to an OutOfMemoryError
.
Next, the error is thrown.
The maximum array length calculation code
If we grep
for the name of the previous method, we will see that the max_length
argument is given by the following in the hotspot/share/oops/arrayOop.hpp file:
// Return the maximum length of an array of BasicType. The length can passed
// to typeArrayOop::object_size(scale, length, header_size) without causing an
// overflow. We also need to make sure that this will not overflow a size_t on
// 32 bit platforms when we convert it to a byte size.
static int32_t max_array_length(BasicType type) {
assert(type >= 0 && type < T_CONFLICT, "wrong type");
assert(type2aelembytes(type) != 0, "wrong type");
const size_t max_element_words_per_size_t =
align_down((SIZE_MAX/HeapWordSize - header_size(type)), MinObjAlignment);
const size_t max_elements_per_size_t =
HeapWordSize * max_element_words_per_size_t / type2aelembytes(type);
if ((size_t)max_jint < max_elements_per_size_t) {
// It should be ok to return max_jint here, but parts of the code
// (CollectedHeap, Klass::oop_oop_iterate(), and more) uses an int for
// passing around the size (in words) of an object. So, we need to avoid
// overflowing an int when we add the header. See CRs 4718400 and 7110613.
return align_down(max_jint - header_size(type), MinObjAlignment);
}
return (int32_t)max_elements_per_size_t;
}
Once again, I don't know C++.
Nor do I know internals of the JVM like HeapWordSize
or MinObjAlignment
.
So take the following assumptions with a grain of salt.
Using the code above, let's try to "manually compute" the maximum allowed length for a Object[]
type.
Given the following:
-
type =
T_OBJECT
we want to create aObject[]
(source) -
SIZE_MAX = 264 - 1
for a 64-bit Linux (glibc) system (source) -
HeapWordSize = 8 bytes
which I assume is true for a 64-bit JVM -
header_size(type) = 2
this value is computed by the formulaheader_size_in_bytes/HeapWordSize
(source).The array header size is 16 bytes: 12 bytes of actual header + 4 bytes of the array length.
I took these values from the JOL output I ran in a previous blog post:
String[1_000] [Ljava.lang.String; object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0) 8 4 (object header: class) 0x00008bd0 12 4 (array length) 1000 12 4 (alignment/padding gap) 16 4000 java.lang.String String;.<elements> N/A Instance size: 4016 bytes Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
The actual post runs JOL for a
String[]
, I assume the header size is the same for aObject[]
. -
MinObjAlignment = 20
the default value forObjectAlignmentInBytes
is 8 bytes. AndMinObjAlignment = ObjectAlignmentInBytes/HeapWordSize
. (source) -
type2aelembytes(type) = 8 bytes
which is the value ofT_OBJECT_aelem_bytes
for a 64-bit system (I assume) (source)
Putting all of the values in SpeedCrunch and we have the following result:
max_elements_per_size_t = 2_305_843_009_213_693_949
The result is much bigger than max_jint
.
max_jint
is defined as 0x7FFFFFFF
which is the same value of Integer.MAX_VALUE
(source).
Therefore, the maximum array length is given by the expression max_jint - header_size(type)
.
In Java code this could be translated to:
var max = Integer.MAX_VALUE - 2;
For completeness, I should mention that, since MinObjAlignment = 1
, the align_down
function will simply return the first argument (as far as I can tell).
Here is the source code of align_down
.
Validating our computed maximum array length
Given our computed maximum value we expect our "Requested array size..." message to occur in two cases only:
new Object[Integer.MAX_VALUE];
new Object[Integer.MAX_VALUE - 1];
Let's assume creating an array with a length of Integer.MAX_VALUE - 2
does not fail with the "VM limit" message.
An array of this size will require more than 8G
of available heap space.
So if we run the program with a heap smaller than that we expect the array creation to fail; except it should fail with a "Java heap space" message instead.
So let's use this fact to our advantage and write the following program:
public class MaxValueTest {
public static void main(String[] args) {
oom(0, "Requested array size exceeds VM limit");
oom(1, "Requested array size exceeds VM limit");
oom(2, "Java heap space");
oom(3, "Java heap space");
System.out.println("Test was sucessful");
}
private static void oom(int value, String expected) {
try {
System.out.println("Will create value=" + value);
@SuppressWarnings("unused")
var a = new Object[Integer.MAX_VALUE - value];
throw new AssertionError("Expected creation error for value=" + value);
} catch (OutOfMemoryError e) {
var m = e.getMessage();
if (!m.equals(expected)) {
throw new AssertionError("""
Expected:
%s
But got:
%s
""".formatted(expected, m));
}
}
}
}
It tries to create four arrays.
It expects an OutOfMemoryError
to occur for all of them.
For the first two, it expects a "Requested array size..." message.
For the last two, it expects a "Java heap space" message instead.
Running the program produces the following output:
Will create value=0
Will create value=1
Will create value=2
Will create value=3
Test was sucessful
Great! Our computation seems to be correct.
Actual maximum array length is not a fixed value
From the "The maximum array length calculation code" section it is possible to conclude that the actual maximum array length is not a fixed value. It depends on a few variables:
-
system architecture;
-
JDK version; and perhaps
-
JVM parameters.
Let's look into them.
System architecture
The maximum array length calculation code uses values that are dependent on the system architecture. In particular:
-
SIZE_MAX;
-
HeapWordSize; and possibly
-
header_size(type).
Remember that, for my 64-bit system, we calculated max_elements_per_size_t
to be:
max_elements_per_size_t = 2_305_843_009_213_693_949
On the other hand, if we were to compute max_elements_per_size_t
for a hypothetical 32-bit system we would get:
max_elements_per_size_t = 1_073_741_820
Which is about half of Integer.MAX_VALUE
(assuming the computation is right).
Therefore the actual maximum array length varies greatly between a 32-bit system and a 64-bit one.
JDK version
As far as I can tell, the actual maximum value is not defined by neither the language specification nor the JVM specification. In other words, we can say it is an "implementation detail".
If this assumption is correct, at some future JDK version can we expect those two values to be equal? I personally think so. This comment in the JDK source code also hints at this possibility. But please, don't take my word for it.
As a "fun fact", in the JDK6 release, it was possible to create an array with the "maximum theoretical value".
I wrote this small program:
public class MaxValueJDK6Test {
public static void main(String[] args) {
Object[] a = new Object[Integer.MAX_VALUE];
System.out.println("Actual array length=" + a.length);
a[0] = "first";
a[Integer.MAX_VALUE - 1] = "last";
System.out.println(a[0]);
System.out.println(a[Integer.MAX_VALUE - 1]);
System.out.println("Test was sucessful");
}
}
And I ran it using JDK6:
$ /opt/jdk/oracle-6/bin/java -version
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)
$ /opt/jdk/oracle-6/bin/java -Xmx16G -cp target/classes/ post.MaxValueJDK6Test
Actual array length=2147483647
first
last
Test was successful
The change seems to have occurred during JDK7. It runs with JDK1.7.0:
$ /opt/jdk/jdk1.7.0/bin/java -version
java version "1.7.0"
Java(TM) SE Runtime Environment (build 1.7.0-b147)
Java HotSpot(TM) 64-Bit Server VM (build 21.0-b17, mixed mode)
$ /opt/jdk/jdk1.7.0/bin/java -Xmx16G -cp target/classes/ post.MaxValueJDK6Test
Actual array length=2147483647
first
last
Test was successful
But fails with JDK1.7.0_80:
$ /opt/jdk/jdk1.7.0_80/bin/java -version
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
$ /opt/jdk/jdk1.7.0_80/bin/java -Xmx16G -cp target/classes/ post.MaxValueJDK6Test
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at post.MaxValueJDK6Test.main(MaxValueJDK6Test.java:20)
Or it could have been backported. I don't know.
But, as mentioned, consider this as a "fun fact".
JVM parameters?
The actual maximum array length possibly depends on the parameters you start your JVM with. I say possibly because I did not verify this one.
From a previous section we saw that MinObjAlignment
is defined by:
MinObjAlignment = ObjectAlignmentInBytes/HeapWordSize
ObjectAlignmentInBytes
is a configurable value you can set when starting your JVM:
$ java -XX:+PrintFlagsFinal | grep ObjectAlignment
intx ObjectAlignmentInBytes = 8 {product lp64_product} {default}
Therefore, depending on how we set this property, we might get a different maximum array length value. But, once again, I did not actually verify this one.
On the object alignment subject I recommend this article by Aleksey Shipilëv.
Conclusion
In this blog post, we discussed about the maximum length of a Java array.
We saw that, in current JVM implementations, the actual maximum length is slightly smaller than the theoretical value. In other words, the actual maximum length is smaller than:
-
Integer.MAX_VALUE
When the specified length is greater than a VM-specific computed maximum value, allocation will fail with an OutOfMemoryError
with the following message:
Requested array size exceeds VM limit
This error will be thrown regardless of how much heap is available.
We saw the JVM code that calculates the maximum allowed array length. Finally, since the value is computed, we discussed the factors that affects its calculation.
You can find the source code of all of the examples in this post in this GitHub repository.