Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/340.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么存储长字符串会导致OOM错误,但将其拆分为短字符串列表不会导致OOM错误?_Java_String_Out Of Memory_Heap Memory - Fatal编程技术网

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

Java 为什么存储长字符串会导致OOM错误,但将其拆分为短字符串列表不会导致OOM错误?,java,string,out-of-memory,heap-memory,Java,String,Out Of Memory,Heap Memory,我有一个Java程序,它使用StringBuilder从输入流构建一个字符串,当字符串过长时,它最终导致内存不足错误。我尝试将其拆分为较短的字符串,并将它们存储在ArrayList中,这样就避免了OOM,即使我尝试存储相同数量的数据。为什么会这样 我的怀疑是,对于一个很长的字符串,计算机必须在内存中为它找到一个连续的位置,但是对于ArrayList,它可以在内存中使用多个较小的位置。我知道Java中的内存可能很棘手,所以这个问题可能没有一个直截了当的答案,但希望有人能让我走上正轨。谢谢 基本上,

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


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

基本上,你是对的

StringBuilder
(更准确地说,
AbstractStringBuilder
)使用
char[]
存储字符串表示(尽管通常
string
不是
char[]
)。虽然Java确实知道数组存储在连续内存中,但很可能是这样。因此,每当向底层数组追加字符串时,都会分配一个新数组,如果它太大,就会抛出
OutOfMemoryError

实际上,执行代码

StringBuilder b = new StringBuilder();
for (int i = 0; i < 7 * Math.pow(10, 8); i++)
    b.append("a"); // line 11
当第3332行
char[]copy=new char[newLength]时数组内到达code>。copyOf
,将引发异常,因为没有足够的内存用于大小为
newLength
的数组

还要注意给出的错误消息:“Java堆空间”。这意味着无法在Java堆中分配对象(在本例中是数组)。(编辑:此错误还有另一个可能的原因,请参阅)

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());
}
然后抛出一个OOME

虽然第一个程序分配的数组单元格不能超过
7*Math.pow(10,8)
,但这一个至少等于
8*Math.pow(10,8)


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

如果您发布了堆栈跟踪(如果可用),则可能会有所帮助。但您观察到的
OutOfMemoryError
很可能是一个原因

(尽管到目前为止,这个答案可能只是一个“有根据的猜测”。没有人能够在不检查系统上发生错误的条件下查明原因)

使用
StringBuilder
连接字符串时,
StringBuilder
将在内部维护一个
char[]
数组,其中包含要构造的字符串的字符

在追加字符串序列时,可能需要在一段时间后增加此
char[]
数组的大小。这最终在
AbstractStringBuilder
基类中完成:

/**
 * This method has the same contract as ensureCapacity, but is
 * never synchronized.
 */
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

/**
 * This implements the expansion semantics of ensureCapacity with no
 * size check or synchronization.
 */
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}
/**
*此方法与ensureCapacity具有相同的契约,但是
*从不同步。
*/
私有空间保证重新通行内部(内部最小通行能力){
//有溢出意识的代码
if(最小容量-value.length>0)
扩展容量(最小容量);
}
/**
*这实现了ensureCapacity的扩展语义,没有
*大小检查或同步。
*/
空隙膨胀能力(int最小能力){
int newCapacity=value.length*2+2;
if(新容量-最小容量<0)
新容量=最小容量;
如果(新容量<0){
if(最小容量<0)//溢出
抛出新的OutOfMemoryError();
newCapacity=Integer.MAX_值;
}
value=Arrays.copyOf(value,newCapacity);
}
每当字符串生成器注意到新数据不适合当前分配的数组时,就会调用它

这显然是一个可能抛出
outofmemory错误的地方。(严格地说,它不一定真的“内存不足”。它只是根据数组可以具有的最大大小检查溢出…)

(编辑:还要看一看:这不一定是错误的来源!您的错误可能确实来自
数组
类,或者更确切地说来自JVM内部)

仔细检查代码时,您会注意到,每次扩展容量时,数组的大小都会增加一倍。这一点至关重要:如果只确保可以追加新的数据块,那么将
n
字符(或其他固定长度的字符串)追加到
StringBuilder
的运行时间将为O(n²)。当尺寸以恒定因子(这里为2)增加时,则运行时间仅为O(n)

然而,这一数字却翻了一番
100000000
100000000
100000000
100000000
100000000
100000000
100000000
100000000
/**
 * This method has the same contract as ensureCapacity, but is
 * never synchronized.
 */
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}

/**
 * This implements the expansion semantics of ensureCapacity with no
 * size check or synchronization.
 */
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}