简体   繁体   English

为什么Java递归调用不会释放局部变量内存

[英]why Java recursion call not release local variable memory

I have a recursion method like below 我有如下的递归方法

private void foo(int i){
    byte[] a = new byte[1 * 1024 * 1024];
    System.out.println(i+" "+a.length);
    foo(i+1);
}

I find the local variable a cannot be released and if I set the maximum heap size 50M (-Xmx50M) it will meet OOM at the 44th call 我发现本地变量a无法释放,如果我将最大堆大小设置为50M(-Xmx50M),它将在第44次调用时满足OOM

44 1048576
java.lang.OutOfMemoryError: Java heap space

but changed it to for loop it does not have this problem 但是将其更改为for循环就没有这个问题

private void bar(){
    int index = 1;
    while (true) {
        byte[] a = new byte[1 * 1024 * 1024];
        System.out.println(index+" "+a.length);
        index += 1;
    }
}

So why in recursion call it not release the memory of local variable? 那么,为什么在递归调用中不释放局部变量的内存呢?

在方法返回后(即执行第一个foo之后)会发生释放,但是在您的情况下,它将调用另一个foo ,后者也会创建一个新的字节数组,并调用第三个foo ,因此它会累加所有字节数组,并且垃圾收集器不会尝试清除它们,直到它们全部返回,或者您获得OOM。

Java does not deallocate local variables from the stack until the method returns. 在方法返回之前,Java不会从堆栈中取消分配局部变量。 So even though the local variable will never be used, it is still referenced, and therefore the array cannot be garbage-collected until the method returns. 因此,即使永远不会使用局部变量,仍会引用该局部变量,因此,在方法返回之前,无法对数组进行垃圾回收。

Also Java does not implement tail call optimisation at all, so there is no early return involved. 而且Java根本不实现尾部调用优化,因此不涉及提前返回。

In for loop variable a is bound to scope of the for loop so reference to array is released after every iteration and garbage collector can deallocate that memory. 在for循环中,变量a限制在for循环的范围内,因此在每次迭代后释放对数组的引用,并且垃圾回收器可以释放该内存。

When you do recurssion references of every array is stored on stack so garbage collector can't destroy those arrays since someone(in this case stack) is referencing those arrays. 当您进行递归操作时,每个数组的引用都存储在堆栈中,因此垃圾收集器无法销毁这些数组,因为有人(在本例中为堆栈)正在引用这些数组。 Imagine this situation: 想象一下这种情况:

private void foo(int i){
    byte[] a = new byte[1 * 1024 * 1024];
    System.out.println(i+" "+a.length);
    foo(i+1);
    a[1] += a[0] + 1;
}

This function demonstrates that even after the recursive call array a could still be used. 此函数演示即使在递归调用数组之后仍可以使用a。

BONUS: The type of recursion you presented is called tail recursion and there are algorithms that can automatically convert those to loops so in languages like Kotlin you could do this: 奖励:您提出的递归类型称为尾递归,并且有一些算法可以自动将其转换为循环,因此在像Kotlin这样的语言中,您可以执行以下操作:

tailrec fun foo(i: Int): Boolean {
    var a = Array<Byte>(1 * 1024 * 1024, { 0 });
    System.out.println("$i ${a.size}");
    foo(i + 1);
}

This would work because Kotlin compiler would turn this into a for or while loop. 这将起作用,因为Kotlin编译器会将其转换为for或while循环。

递归在这种情况下不起作用,因为Java必须将方法的每个实例都存储在内存中,因此当您递归调用它时,它将用完ram,但是当您使用循环时,它可以使用无法使用的优化递归。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM