Java内存溢出错误

7
为什么下面的代码会造成死锁?
List<Object> list = new ArrayList<>();
while (true) {
    for(int i = 0; i < 1000000; i++){
        list.add(new Object());
    }
}

引发内存不足错误

但是这段代码没有引发此类错误

while(true) {
    List<Object> list = new ArrayList<>();
    for(int i = 0; i < 1000000; i++){
        list.add(new Object());
    }
}

我看到这与列表是在while循环内部还是外部创建有关,但我不确定发生这种情况的原因


4
一个简单的内存泄漏... 对于第二个示例,您可以简化垃圾收集器的工作。 - FieryCat
Java使用堆来存储垃圾收集器中的新对象。当堆已满且垃圾收集器无法容纳新对象时,会抛出OutOfMemoryError错误。当没有足够的本机内存来加载Java类时,也可能会抛出此错误。 - Jay Smith
1
@FieryCat 不要简化... 在第一种情况下,根本没有任何需要进行垃圾回收的东西。 - wvdz
@wvdz,是的,可能我并不完全正确...但无限增长的列表是一件相当明显的事情;如果这不是你期望的,那就是一个GC问题。 - FieryCat
8个回答

10

在第一种情况下,您有一个单一的 ArrayList 实例,并不断添加新的 Object 实例,直到内存用尽。

在第二种情况下,您在每个 while 循环迭代中创建一个新的 ArrayList,并向其中添加 1000000Object 实例,这意味着先前迭代创建的 ArrayList 和其中包含的 1000000Object 实例可以被垃圾回收,因为程序不再引用它们。

请注意,如果新的 Object 实例比垃圾回收器释放旧实例的速度更快,则第二个代码段也会导致内存不足错误,但这取决于JVM实现。


5
在第一个片段中,列表是在循环外创建(并保留!),因此您只需不断向其中添加元素,直到消耗所有可用内存。
在第二个片段中,每次while循环迭代都会创建一个新的ArrayList对象。由于一旦迭代结束就不再持有对该实例的引用,因此这个列表有资格进行垃圾回收,所以旧列表会被释放,您不会耗尽内存。

4

在第二种情况中,创建的列表(并添加元素)超出了范围并且可以进行垃圾回收。这将被回收以为新的ArrayList腾出内存。因此,对于每次迭代,都会创建一个新的ArrayList,然后在循环结束时变得可以进行垃圾回收。因此,当内存不足时,这些对象将被垃圾回收。

在第一种情况下,您正在向同一个ArrayList添加元素。没有任何东西被垃圾回收。


3
这个问题已经被@Eran、@TheLostMind等人很好地回答了,所以我不会重复。我只想借此机会指出SoftReferenceWeakReference如何帮助“延迟”内存溢出异常。
请使用JVM参数-Xms64m -Xmx64m运行下面的代码,以便您可以快速查看结果。
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class OOM {
    public static void main(String[] args) {
        System.out.println(new Date());
        try {
            scenario1(false, false); // in my box, OOM occurred with average of 2 seconds.
            //scenario1(true, false); // in my box, OOM occurred average of 6 seconds.
            //scenario1(false, true); // in my box, OOM occurred average of 8 seconds.
        } catch (Exception e) {
        } catch (Error err){

        }
        System.out.println(new Date());
    }

    private static void scenario1(boolean useSoftReference, boolean useWeakReference) {
        List<Object> list = new ArrayList<>();
        while (true) {
            for(int i = 0; i < 1000000; i++){
                if(useSoftReference){
                    list.add(new SoftReference<Object>(new Object()));
                } else if(useWeakReference){
                    list.add(new WeakReference<Object>(new Object()));
                } else{
                    list.add(new Object());
                }
            }
        }
    }
}

3
因为当你在while循环中创建列表时,你的先前列表会被清空并创建一个新的空列表。之后,Java垃圾回收器释放了你的内存,并向列表添加了1000000个元素。然后又创建了一个新的列表,一切都重复了。

3
在第一种情况下,列表对象在 while 循环外部声明,而该循环又无限运行(如 while (true) ),因此它会不断添加,直到内存耗尽。而在第二种情况下,由于您已将列表声明在 while 内部,因此最大大小受限于 for 循环的迭代次数。
每次 for 循环结束时,列表对象都会被重置,也就是创建一个新的列表对象用于添加元素,因此您有了上限。旧的对象将被垃圾回收,从而清除 JVM。

2
在第一个示例中,您创建一个列表,向其中添加项目,然后循环结束。在第二个示例中,您创建一个列表,向其中添加内容,然后创建一个列表,向其中添加许多内容并无限重复。由于在第一个示例中,您的变量是在循环外创建的,因此只有一个要填充的列表。

2
两段代码唯一的区别在于List list = new ArrayList<>();这一行的位置。第一个代码中,ArrayList在while循环外部声明并且持续向同一个ArrayList实例添加无数个对象,因此会发生内存溢出。而第二个代码在while循环内部声明ArrayList,所以每个循环周期都会实例化一个新的ArrayList(许多ArrayList实例)。根据Java中垃圾收集器的规则,由于不再有指针指向上一个周期的实例,因此它们将被删除。因此,Java中的GC防止了第二种情况下的内存溢出。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接