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

标签 java string out-of-memory heap-memory

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

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

最佳答案

基本上,你是对的。

StringBuilder(更准确地说,AbstractStringBuilder)使用 char[] 来存储字符串表示(尽管通常是 String 不是 char[])。而 Java 做 not guarantee一个数组确实存储在连续的内存中,很可能是这样。因此,每当将字符串附加到底层数组时,都会分配一个新数组,如果它太大,则会抛出 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)

当在 Arrays.copyOf 中到达第 3332 行 char[] copy = new char[newLength]; 时,抛出异常,因为没有足够的内存用于大小为 newLength 的数组。

另请注意与错误一起给出的消息:“Java 堆空间”。这意味着无法在 Java 堆中分配对象(在本例中为数组)。 (编辑:此错误还有另一个可能的原因,请参阅 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.

将数组分成总大小相同的较小数组可避免 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 的大小在系统之间不是恒定的。

关于java - 为什么存储长字符串会导致 OOM 错误,但将其分解为短字符串列表却不会?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45406035/

相关文章:

python - 用translate代替标点符号,这3种方式有什么区别?

python - 合并多个大型 DataFrame 的有效方法

java - 如何仅更改日历对象中的月,年和日期而不更改小时,分钟和秒?

java - Rome XmlReader 不读取 https 提要

java - 如何将三个 fragment 的数据发送到单个 Activity ?

string - 取消缩进多行字符串值

C++ : Pick portions/data from a string having fixed format

java - 我想用Java读取两个文件,一个.xlsx和一个.csv,并希望根据文件的扩展名在控制台上显示内容

在最大堆中分配字节 [] 时出现 Java OutOfMemoryError

php - CodeIgniter 扩展多个 Controller ?