当前位置: 首页 > 知识库问答 >
问题:

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

龙俊美
2023-03-14

我有一个像下面这样的递归方法

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

我发现本地变量A无法释放,如果我将最大堆大小设置为50M(-xmx50M),它将在第44次调用时满足OOM

44 1048576
java.lang.OutOfMemoryError: Java heap space

但将其更改为for循环则不存在此问题

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

那么为什么在递归中叫它不释放局部变量的内存呢?

共有2个答案

吉毅
2023-03-14

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

卫嘉佑
2023-03-14

在for循环中,变量a绑定到for循环的作用域,因此在每次迭代后释放对数组的引用,垃圾回收器可以释放该内存。

在进行递归时,每个数组的引用都存储在堆栈上,因此垃圾回收器无法销毁这些数组,因为有人(在本例中是堆栈)正在引用这些数组。想象一下这种情况:

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;
}

这个函数说明,即使在递归调用之后,数组a仍然可以使用。

加分:您所介绍的递归类型称为尾递归,有一些算法可以自动将其转换为循环,所以在Kotlin这样的语言中,您可以这样做:

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

这是可行的,因为Kotlin编译器会将其转换为for或while循环。

 类似资料:
  • 函数甚至可以不使用局部变量来调用自己. 例子 23-14. 汉诺塔 1 #! /bin/bash 2 # 3 # 汉诺塔(The Towers Of Hanoi) 4 # Bash script 5 # Copyright (C) 2000 Amit Singh. All Rights Reserved. 6 # http://hanoi.kernelthrea

  • 问题内容: Java的设计者是否有任何理由认为不应为局部变量提供默认值?认真地讲,如果实例变量可以被赋予默认值,那为什么我们不能对局部变量做同样的事情呢? 问题答案: 声明局部变量主要是为了进行一些计算。因此,程序员决定设置变量的值,并且不应采用默认值。如果程序员错误地没有初始化局部变量并且使用默认值,则输出可能是一些意外值。因此,在使用局部变量的情况下,编译器将要求程序员在访问变量之前使用一些值

  • 即使不适用局部变量,函数也可以递归的调用自身。 例子24-16. 斐波那契序列 #!/bin/bash # fibo.sh : 斐波那契序列 (递归) # 作者: M. Cooper # License: GPL3 # ----------算法-------------- # Fibo(0) = 0 # Fibo(1) = 1 # else # Fibo(j) = Fibo(j-1) + Fi

  • 想改进这个问题吗 通过编辑这篇文章,更新问题,以便用事实和引文来回答。 我有以下递归方法: 在本例中,当我调用其中的方法时,以及使用方法I时,会再次创建parentUuids,并且会丢失其值。 另一方面,当使用如下所示的方法II时,我保留了值: 我的问题是: 1.方法II是保留局部变量值的正确方法吗? 2.这是一种特殊的用法吗?它叫什么名字?我以前从未使用过这种方法,但我认为它可能对这种情况有用。

  • 我有一个(co?)递归函数对,它们处理元组列表,并根据一些开始和结束条件将它们折叠成批处理。 我做得不多,所以我可能很愚蠢。 我已经修改了一个简单的非尾部递归版本,通过明确引入一个“tot”参数来构成当前折叠状态,我认为这是尾部递归的,但我在大输入上得到了可怕的堆栈溢出。。。。(在调试器和(调试)中)。exe) 作为一个明确的折叠,可能有更好的方法来做到这一点...但这几乎不是重点,重点是为什么它