当看似无关的代码块注释掉时OutOfMemoryError

时间:2014-01-29 17:02:30

标签: java out-of-memory

当有OutOfMemoryError循环被注释掉时,有人可以解释为什么这个程序会抛出for吗?如果它被取消注释,则运行正常。

抛出的异常是:

  

线程“main”中的异常java.lang.OutOfMemoryError:Java堆空间

public class JavaMemoryPuzzlePolite
{
    private final int dataSize = (int)(Runtime.getRuntime().maxMemory()* 0.6);

    public void f()
    {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];
        }

        /*
        for(int i = 0; i < 10; i++) {
            System.out.println("Please be so kind and release memory");
        }
        */

        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

    public static void main(String []args)
    {
        JavaMemoryPuzzlePolite jmp = new JavaMemoryPuzzlePolite();
        jmp.f();
    }
}

3 个答案:

答案 0 :(得分:4)

我已经使用许多不同类型的代码片段对此进行了调查,这些代码片段可以插入到您的注释中,并且唯一不会导致OutOfMemoryError的代码类型是为局部变量赋值的代码。

这是对我最有意义的解释:

当你有

byte[] data = new byte[dataSize];

bytecode instructions

    12: newarray       byte
    14: astore_1 

newarray创建一个新数组,astore_1在局部变量1中存储对它的引用。

在此之后,该变量的范围将丢失,但字节码没有说明其值被清除的任何内容,因此存在对堆栈帧中剩余的该对象的引用。即使代码本身无法访问它,这个特定的垃圾收集器也认为它是可以访问的。

如果您尝试分配另一个局部变量,例如

byte i = 1;

然后相应的字节代码指令类似于

    15: iconst_1      
    16: istore_1   

其中iconst_1将值1存储在堆栈上,istore_1将值存储在变量1中,这似乎与以前的变量相同。如果是,那么您将覆盖其值,对byte[]对象的引用将丢失,然后该对象“变得”符合垃圾回收的条件。

最终证明

使用-g选项

编译此代码
public class Driver {
    private static final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public static void main(String[] args) throws InterruptedException {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];

        }
        byte i = 1;
        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

}

然后运行javap -c -l Driver。你会看到LocalVariableTable这样的

LocalVariableTable:
  Start  Length  Slot  Name   Signature
   15       0     1    data   [B
    0      33     0    args   [Ljava/lang/String;
   17      16     1     i     B
   32       1     2    data2  [B

其中,slot是astore_1istore_1中的索引。因此,您可以看到,在为局部变量分配新值时,将清除对byte[]的引用。即使变量具有不同的类型/名称,在字节码中,它们也存储在同一个地方。

答案 1 :(得分:3)

由于用户Sotirios Delimanolis在评论中提到的答案表明,这与第一个data数组的范围有关。简化一下,代码

{
    byte[] data = new byte[bigNumber];
}
byte[] data = new byte[bigNumber];

编译(简化和使用英语)

1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make an array of size bigNumber
4: store a reference to the created array into the 1st local variable

这会耗尽内存,因为在第3步中60%的内存已被占用,我们正在尝试创建一个占用60%以上内存的阵列。旧的字节数组不能被垃圾收集,因为它仍然在第一个局部变量中被引用。

然而代码

{
    byte[] data = new byte[bigNumber];
}
someOtherVariable = somethingSmall;
byte[] data = new byte[bigNumber];

编译到

1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make somethingSmall
4: store a reference to somethingSmall into the 1st local variable
5: make an array of size bigNumber
6: store a reference to the created array into the 2nd local variable

这里在第四步,取消引用初始大字节数组。因此,当您尝试在第五步创建另一个大型数组时,它会成功,因为它可以垃圾收集旧数组以腾出空间。

答案 2 :(得分:0)

当你对for循环取消注释时,在执行循环时,JVM会有足够的时间释放为data分配的内存,因为变量的范围已经结束,变量无法访问。

当您注释掉for循环时,JVM将没有太多时间来释放变量data

使用的内存
相关问题