当看似无关的代码块注释掉时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_1
和istore_1
的索引。 因此,您可以看到,在为局部变量分配新值时,将清除对byte[]
的引用。 即使变量具有不同的类型/名称,在字节码中,它们也存储在同一个地方。
正如用户Sotirios Delimanolis在评论中所提到的答案所暗示的那样,这与第一个data
arrays的范围有关。 简化一下,代码
{ 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
使用的内存