Java中的垃圾收集与递归函数

时间:2015-01-05 14:01:08

标签: java recursion garbage-collection

我知道在常规循环的每次迭代中,对象变得无法访问并标记为垃圾收集。那么递归调用呢?类似的东西:

public void doWork() {

    Object a = new Object();

    ....some work with a...

    this.sleep(60000);
    doWork();


  }

第二次递归开始后第一次递归中的对象(即' a')是否标记为垃圾回收,或者是否需要显式标记为null,因为外部函数由于递归而永远不会完成。

6 个答案:

答案 0 :(得分:3)

在每次递归调用期间,局部变量(此处为引用" a")被压入堆栈。局部变量是GC根。在第二次递归调用期间,新引用被压入堆栈。但是,第一个引用仍然存在,因此对象仍然可以访问,因此无法进行垃圾回收。

因此,如果您希望将第一个创建的对象标记为垃圾收集(虽然该函数尚未完成),您应该明确设置" a"为空。

以下是了解GC的有用链接:http://javabook.compuware.com/content/memory/how-garbage-collection-works.aspx

答案 1 :(得分:1)

我发现,如果不再需要引用对象的局部变量,则会收集垃圾。但是,这在调试时不适用。我猜调试器会保留对它的引用,但在常规执行中不需要它。

尝试执行以下代码,看看会发生什么。

Object o1 = new Object();
System.out.println(o1);
WeakReference<Object> wr = new WeakReference<Object>(o1);
System.gc();
System.out.println(wr.get());

我的输出(没有调试器):

java.lang.Object@20662d1
null

我的输出(带调试器):

java.lang.Object@206b4fc
java.lang.Object@206b4fc

因此,即使本地方法仍在堆栈中,也会显示早期引用是垃圾回收。

答案 2 :(得分:1)

  

第二次递归开始后第一次递归中的对象(即“a”)是否标记为垃圾回收,或者是否需要显式标记为null,因为外部函数由于递归而永远不会完成。

答案是......它取决于平台。

变量a仍然在范围内,直到doWork()的声明实例返回。另一方面,我们很明显,到达递归调用时a变量不能影响计算,并且(理论上)这意味着GC不再需要考虑它确定可达性的目的。

因此,它归结为JVM是否足够聪明,意识到a不再重要。这取决于平台。正如另一个答案所指出的那样,附带调试器的运行可以改变这一点。


另一个答案提到了尾部调用优化。当然,如果JVM确实实现了这种优化,那么“旧”a在概念上将被优化的“调用”中的“新”替换。但是,没有HotSpot JVM(至少到Java 8)实现尾调用优化,因为优化会干扰依赖于能够计算堆栈帧的Java代码;例如安全码。

但是,如果此特定代码 是一个重大的存储泄漏,那么它很可能没有实际意义,因为(缺少尾调用优化)代码可能会给你一个{{1}在它填满堆之前。

答案 3 :(得分:0)

只要方法本身在调用堆栈中,您应该假设所有本地引用都是活动的。这意味着每次递归都会获得一个对a的活动引用。

运行时可能会发现,当不再访问变量a时,可以覆盖变量a的槽。在这种情况下,即使doWork()尚未返回,它也变得无法访问。但是你不能依赖于此。在最后一次使用。

后立即a = null;会有所帮助

答案 4 :(得分:0)

理论上,递归是通过将实际帧(局部变量集)推入堆栈并为新执行打开新帧来完成的。

堆中的对象仍然被推送到堆栈的框架中的变量引用,这意味着对于GC,对象仍然是#34; live&#34;。

然而,许多编译器(我也假设javac)能够识别和展开类似示例中的尾递归,从而简化了递归,释放了许多资源。

但是,一般来说,递归占用了大量的堆栈和堆空间,这就是为什么使用递归并不总是一个好主意。

答案 5 :(得分:0)

如果您想要垃圾收集,通过将变量设置为null,这是一种积极摆脱引用的好方法。但是,不要忘记对象可能包含事件注册,单独的线程等等,因此如果要对对象进行垃圾收集,则必须确保其引用中没有泄漏。 Object不是这种情况,但是其他类的实例可能会发生这种情况。至于问题,我相信它不是垃圾收集,除非你积极摆脱它的引用,但我没有用所有Java版本测试它。