Essentially, you are correct.
A StringBuilder
(more precisely, AbstractStringBuilder
) uses a char[]
to store the string representation (though generally a String
is not a char[]
). While Java does not guarantee that an array is indeed stored in contiguous memory, it most probably is. Thus, whenever appending strings to the underlying array, a new array is allocated and if it is too large, an OutOfMemoryError
is thrown.
Indeed, executing the code
StringBuilder b = new StringBuilder();
for (int i = 0; i < 7 * Math.pow(10, 8); i++)
b.append("a"); // line 11
throws the exception:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
at test1.Main.main(Main.java:11)
When line 3332 char[] copy = new char[newLength];
is reached inside Arrays.copyOf
, the exception is thrown because there is not enough memory for an array of size newLength
.
Note also the message given with the error: "Java heap space". This means that an object (an array, in this case) could not be allocated in the Java heap. (Edit: there is another possible cause for this error, see Marco13's answer).
2.5.3. Heap
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
... The memory for the heap does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
The following exceptional condition is associated with the heap:
- If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an
OutOfMemoryError
.
Breaking the array into smaller arrays of the same total size avoids the OOME because each array can be stored separately in a smaller contiguous area. Of course, you "pay" for this by having to point from each array to the next one.
Compare the above code with this one:
static StringBuilder b1 = new StringBuilder();
static StringBuilder b2 = new StringBuilder();
...
static StringBuilder b10 = new StringBuilder();
public static void main(String[] args) {
for (int i = 0; i < Math.pow(10, 8); i++)
b1.append("a");
System.out.println(b1.length());
// ...
for (int i = 0; i < Math.pow(10, 8); i++)
b10.append("a");
System.out.println(b10.length());
}
The output is
100000000
100000000
100000000
100000000
100000000
100000000
100000000
100000000
and then an OOME is thrown.
While the first program could not allocate more than 7 * Math.pow(10, 8)
array cells, this one sums up to at least 8 * Math.pow(10, 8)
.
Note that the size of the heap can be changed with VM initialization parameters, so the size which will throw the OOME is not constant between systems.