Java 注释掉看似无关的代码块时发生OutOfMemoryError
有人能解释一下为什么当Java 注释掉看似无关的代码块时发生OutOfMemoryError,java,out-of-memory,Java,Out Of Memory,有人能解释一下为什么当for循环被注释掉时,这个程序会抛出OutOfMemoryError?如果未注释,则运行良好 引发的异常是: 线程“main”java.lang.OutOfMemoryError中出现异常:java堆空间 公共类JavaMemoryPuzzlePolite { private final int dataSize=(int)(Runtime.getRuntime().maxMemory()*0.6); 公共空间f() { { System.out.println(数据大小)
for
循环被注释掉时,这个程序会抛出OutOfMemoryError
?如果未注释,则运行良好
引发的异常是:
线程“main”java.lang.OutOfMemoryError中出现异常:java堆空间
公共类JavaMemoryPuzzlePolite
{
private final int dataSize=(int)(Runtime.getRuntime().maxMemory()*0.6);
公共空间f()
{
{
System.out.println(数据大小);
字节[]数据=新字节[数据大小];
}
/*
对于(int i=0;i<10;i++){
System.out.println(“请善待并释放内存”);
}
*/
System.out.println(数据大小);
字节[]数据2=新字节[数据大小];
}
公共静态void main(字符串[]args)
{
JavaMemoryPuzzlePolite jmp=新的JavaMemoryPuzzlePolite();
jmp.f();
}
}
如果未注释for循环,则在执行循环时,JVM会有足够的时间释放分配给数据的内存,因为变量的作用域已结束,并且变量变得不可访问
当您注释掉for循环时,JVM将没有太多时间来释放varibale数据所使用的内存,正如用户Sotirios Delimanolis在注释中链接到的答案所示,这与第一个数据
数组的范围有关。简化一点,代码
{
byte[] data = new byte[bigNumber];
}
byte[] data = new byte[bigNumber];
编译为(简化并使用英语)
这会耗尽内存,因为在第三步,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
在第四步中,初始大字节数组被解引用。因此,当您在第五步尝试创建另一个大数组时,它会成功,因为它可以垃圾收集旧数组以腾出空间。我已经研究了许多不同类型的代码段,这些代码段可以插入注释所在的位置,唯一不会导致OutOfMemoryError
的代码类型是分配一些将值设置为局部变量
这是对我最有意义的解释:
当你有
byte[] data = new byte[dataSize];
这些是
其中newarray
创建一个新数组,并且astore_1
将对它的引用存储在局部变量1中
在此之后,该变量的作用域丢失,但字节码没有说明其值被清除的任何内容,因此堆栈帧中仍然存在对该对象的引用。这个特定的垃圾收集器认为它是可访问的,即使代码本身无法访问它
如果您尝试分配另一个局部变量,例如
byte i = 1;
那么相应的字节码指令如下
15: iconst_1
16: istore_1
其中,iconst_1
将值1存储在堆栈上,istore_1
将该值存储在变量1中,该变量似乎与之前的变量相同。如果是,那么您将覆盖其值,对字节[]
对象的引用将丢失,然后该对象将“成为”符合垃圾收集条件的对象
最终证明
使用-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驱动程序
。您将看到一个类似这样的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\u 1
和istore\u 1
中的索引。因此,当您为局部变量分配新值时,对字节[]
的引用被清除。即使变量具有不同的类型/名称,在字节码中,它们也存储在相同的位置 在我的机器上编译得很好,不管有没有注释过的代码为什么在f()后面有额外的括号?@mdewitt它只是一个块。线程“main”java.lang.OutOfMemoryError中的异常:java堆空间它在这两种情况下都能正确编译。它运行时没有异常,循环未注释,但对我来说失败,OutOfMemoryError注释掉了循环。不可否认,如果代码缩进得更清楚,就更容易理解,但这没关系。如果您要说有错误,您应该始终说明错误是什么。+1:我不确定,但是对System.out.println()的调用使用了相同的局部变量?@MartijnCourteaux thedataSize
?它是一个实例变量。不,我的意思是在println的执行过程中,使用了同一个插槽。但是现在我看到int I
int循环可能是使用相同插槽的变量。@MartijnCourteaux是的,没有发布该循环的字节码,因为for
循环涉及很多指令,但是变量I
将占用局部变量表中的相同插槽。下面是一些算法,可用于为插槽分配变量。图形的顶点是变量,插槽由颜色表示。顶点之间的边表示两个连接的变量同时处于活动状态。请注意,在for循环中,我们声明了一个新变量i
。为i
分配内存时,GC正在垃圾收集为字节数组数据
分配的内存。如果变量是在定义数据之前定义的i
,并且取消对for循环的注释,即使这样,您也会得到OutOfMemoryError
变量与GC有什么关系?你的回答是关于时间的。在什么意义上?
15: iconst_1
16: istore_1
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];
}
}
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