从本质上讲,你是对的。
A(更准确地说,)使用 a 来存储字符串表示形式(尽管通常 a 不是 )。虽然Java不能保证数组确实存储在连续的内存中,但它很可能是。因此,每当将字符串追加到基础数组时,都会分配一个新数组,如果它太大,则抛出一个。StringBuilder
AbstractStringBuilder
char[]
String
char[]
OutOfMemoryError
实际上,执行代码
StringBuilder b = new StringBuilder();
for (int i = 0; i < 7 * Math.pow(10, 8); i++)
b.append("a"); // line 11
引发异常:
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)
当内部到达第 3332 行时,将引发异常,因为没有足够的内存来容纳大小数组。char[] copy = new char[newLength];
Arrays.copyOf
newLength
还要注意错误给出的消息:“Java堆空间”。这意味着无法在 Java 堆中分配对象(在本例中为数组)。(编辑:此错误还有另一个可能的原因,请参阅Marco13的答案)。
2.5.3. 堆
Java 虚拟机具有一个在所有 Java 虚拟机线程之间共享的堆。堆是运行时数据区域,从中为所有类实例和数组分配内存。
...堆的内存不需要是连续的。
Java 虚拟机实现可以为程序员或用户提供对堆的初始大小的控制,以及如果堆可以动态扩展或收缩,则可以控制最大和最小堆大小。
以下异常情况与堆相关联:
- 如果计算需要的堆数超过了自动存储管理系统所能提供的堆数,则 Java 虚拟机会抛出一个
OutOfMemoryError
。
将数组分解为具有相同总大小的较小数组可避免使用 OOME,因为每个数组都可以单独存储在较小的连续区域中。当然,您为此“付费”,必须从每个数组指向下一个数组。
将上面的代码与下面的代码进行比较:
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());
}
输出为
100000000
100000000
100000000
100000000
100000000
100000000
100000000
100000000
然后抛出一个 OOME。
虽然第一个程序不能分配超过数组单元格,但这个程序至少可以总结为 。7 * Math.pow(10, 8)
8 * Math.pow(10, 8)
请注意,可以使用 VM 初始化参数更改堆的大小,因此将引发 OOME 的大小在系统之间不是恒定的。