为什么存储长字符串会导致OOM错误,但将其分解为短字符串列表却不会?

Rex*_*ana 11 java string heap-memory out-of-memory

我有一个Java程序,它使用a StringBuilder来从输入流构建一个字符串,并最终在字符串太长时导致内存不足错误.我尝试将其分解为更短的字符串并将其存储在一个中ArrayList,这避免了OOM,即使我试图存储相同数量的数据.为什么是这样?

我怀疑有一个非常长的字符串,计算机必须在内存中找到一个连续的位置,但是ArrayList它可以在内存中使用多个较小的位置.我知道Java中的内存可能很棘手,所以这个问题可能没有直截了当的答案,但希望有人可以让我走上正轨.谢谢!

use*_*551 6

基本上,你是对的.

A StringBuilder(更确切地说,AbstractStringBuilder)使用a char[]来存储字符串表示(尽管通常String不是a char[]).虽然Java并不能保证数组确实存储在连续的内存中,但很可能是.因此,每当将字符串附加到底层数组时,就会分配一个新数组,如果它太大,OutOfMemoryError则抛出一个数组.

的确,执行代码

StringBuilder b = new StringBuilder();
for (int i = 0; i < 7 * Math.pow(10, 8); i++)
    b.append("a"); // line 11
Run Code Online (Sandbox Code Playgroud)

抛出异常:

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)
Run Code Online (Sandbox Code Playgroud)

char[] copy = new char[newLength];到达内部的行3332时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());
}
Run Code Online (Sandbox Code Playgroud)

输出是

100000000
100000000
100000000
100000000
100000000
100000000
100000000
100000000
Run Code Online (Sandbox Code Playgroud)

然后抛出一个OOME.

虽然第一个程序不能分配超过7 * Math.pow(10, 8)阵列单元格,但这个程序至少总结8 * Math.pow(10, 8).

请注意,可以使用VM初始化参数更改堆的大小,因此抛出OOME的大小在系统之间不是恒定的.