当有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();
}
}
答案 0 :(得分:4)
我已经使用许多不同类型的代码片段对此进行了调查,这些代码片段可以插入到您的注释中,并且唯一不会导致OutOfMemoryError
的代码类型是为局部变量赋值的代码。
这是对我最有意义的解释:
当你有
时byte[] data = new byte[dataSize];
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_1
和istore_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