添加For循环可防止OutOfMemoryError

sna*_*pal 27 java jvm

当我删除for循环时,我得到一个OutOfMemoryError.当我使用for循环时,我没有得到错误.任何人都可以帮助我理解这种行为吗?

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 < 1; 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Tag*_*eev 29

f()方法在解释的帧中执行.解释的帧的行为与JIT编译的帧不同.以下是没有for循环的伪代码的样子:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Allocate dataSize bytes of memory
4. Store it into variable slot #1
Run Code Online (Sandbox Code Playgroud)

所以你有OutOfMemoryError第3步,因为旧byte[]数组仍然存在于变量#1中.但是添加for循环(实际添加i变量)会使事情变得不同:

1. Allocate dataSize bytes of memory
2. Store it into variable slot #1
3. Store 0 to slot #1 (thus byte[] array is now eligible for GC)
4. Do the for loop
5. Allocate dataSize bytes of memory
6. Store it into variable slot #2
Run Code Online (Sandbox Code Playgroud)

这里,当您在步骤#5分配新数组时,第一个数组已经可以进行垃圾回收.

请注意,JIT编译器可能表现得更聪明,并且在变量未使用时将第一个数组与变量取消链接(在您的特定情况下,它根本不会分配它).

另请注意,在您的特定情况下,结果取决于java编译器.ECJ(Eclipse编译器)非常聪明,不会将第一个数组存储到变量中,因为它没有被使用.因此,OutOfMemoryError即使没有for循环,也不会进入ECJ编译的类.

有关更多详细信息,您可以查看javap实用程序提供的字节码反汇编输出,并查看如何重用变量槽.

  • @asgs,每个局部变量都有一个由javac编译器分配的槽.`data`被分配给插槽#1.然后阻塞结束,因此可以重复使用插槽,下一个变量也将具有插槽#1.对于for循环,下一个变量是`i`.因此`int i = 0`被翻译成`iconst_0/istore_1`,在我的答案中是"将0存储到插槽#1".解释器并不关心它是否实际上是一个新变量,它只是替换了以前的值,因此它变得没有引用. (3认同)
  • @asgs,nope.`iconst_0`表示"将常量0加载到堆栈顶部",而"istore_1"表示"弹出堆栈值的顶部并将其存储到本地变量#1". (3认同)