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 the
dataSize
?它是一个实例变量。不,我的意思是在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