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

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

抛出的exception是:

线程“main”中的exceptionjava.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(); } } 

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

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

当你有

 byte[] data = new byte[dataSize]; 

字节码指令是

  12: newarray byte 14: astore_1 

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

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

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

 byte i = 1; 

然后相应的字节码指令就像

  15: iconst_1 16: istore_1 

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

最后的certificate

使用-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[]的引用。 即使变量具有不同的类型/名称,在字节码中,它们也存储在同一个地方。

正如用户Sotirios Delimanolis在评论中所提到的答案所暗示的那样,这与第一个dataarrays的范围有关。 简化一下,代码

 { 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%以上内存的arrays。 旧的字节数组不能被垃圾收集,因为它仍然在第一个局部变量中引用。

但是代码

 { 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 

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

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

当您注释掉for循环时,JVM将没有太多时间来释放varibale data使用的内存