Java内存不足错误

时间:2017-06-14 05:58:07

标签: java out-of-memory

为什么这个代码

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循环内部或在其外部创建的列表有关,但我不确定为什么这种情况发生的原因。

8 个答案:

答案 0 :(得分:10)

在第一种情况下,您只有一个ArrayList实例,并且一直在向它添加新的Object实例,直到内存不足为止。

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

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

答案 1 :(得分:5)

在第一个片段中,列表在循环外部创建(并保留!),因此您只需不断向其中添加元素,直到消耗掉所有可用内存为止。

在第二个片段中,while循环的每次迭代都会创建一个新的ArrayList对象。由于您在迭代结束后不再持有对该实例的引用,因此此列表符合垃圾回收的条件,因此旧列表不断被释放,您不会耗尽您的记忆。

答案 2 :(得分:4)

在第二种情况下,创建的列表(以及添加元素的位置)超出范围且符合GC条件。这将用于为新ArrayList创建内存。因此,对于每次迭代,都会创建一个新的ArrayList,然后它就有资格获得GC(当循环结束时)。因此,当内存不足时,这些对象将被GCed。

在第一种情况下,您要将元素添加到同一ArrayList。什么都没有GC。

答案 3 :(得分:3)

因为当您在while循环中创建列表时,您的上一个列表将被转储,并且您有一个新的空列表。然后你的内存被java垃圾收集器释放,你将1000000个元素添加到列表中。然后创建一个新列表,一切都重复。

答案 4 :(得分:3)

在第一个场景中,列表对象在while循环之外声明,它再次无限期地运行(因为while(true)),因此它一直在添加,直到它耗尽内存,而在第二个因为你已经在while内声明了列表,最大大小被限制为for循环的迭代次数。

每次for循环存在时,重置列表对象,即创建新的列表对象,您开始添加,因此您有一个上限。旧对象被垃圾收集,从而清除JVM。

答案 5 :(得分: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());
                }
            }
        }
    }
}

答案 6 :(得分:2)

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

答案 7 :(得分:2)

两个代码之间的唯一区别是List list = new ArrayList&lt;&gt;();线。对于第一个代码,在while循环之外声明的ArrayList并且它不断地将无限数量的对象添加到一个ArrayList实例中,因此发生内存不足。另一方面,第二个在while循环内部声明ArrayList,因此它将在每个循环周期(许多ArrayList实例)之后实例化一个新的ArrayList。根据Java中的垃圾收集器规则,之前的循环实例将被删除,因为它不再被指向。因此,Java中的GC可防止第二种情况下的内存不足。