使用递归函数在 Java 中进行垃圾收集
Garbage Collection in Java with Recursive Function
我知道在常规循环的每次迭代中,对象变得不可访问并被标记为垃圾回收。递归调用呢?类似于:
public void doWork() {
Object a = new Object();
....some work with a...
this.sleep(60000);
doWork();
}
第一次递归中的对象(即'a')是否在第二次递归开始后标记为垃圾回收,或者是否需要显式标记为空,因为外部函数由于递归而永远不会完成。
在每次递归调用期间,局部变量(此处引用 "a")被压入堆栈。局部变量是 GC 根。在第二次递归调用期间,新引用被压入堆栈。但是,第一个引用仍然存在,因此该对象仍然可以访问,因此不能被垃圾回收。
因此,如果您希望第一个创建的对象被标记为垃圾回收(在函数尚未完成时),您应该明确地将 "a" 设置为 null。
这里有一个有用的link理解GC:http://javabook.compuware.com/content/memory/how-garbage-collection-works.aspx
您应该假设只要方法本身在调用堆栈中,所有本地引用都是有效的。这意味着您每次递归都会获得一个对 a 的有效引用。
运行时可能会发现,当不再访问变量 a 的槽时,它可能会被覆盖。在那种情况下,即使 doWork() 尚未返回,它也变得无法访问。但是你不能依赖这个。在上次使用 a.
后立即帮助 a = null;
理论上,递归是通过将实际的帧(局部变量集)压入栈中,并为新的执行打开一个新的帧来完成的。
堆中的对象仍然被压入堆栈的帧中的变量所引用,这意味着对于GC来说,对象仍然是"live"。
然而,许多编译器(我假设 javac 也是如此)能够像示例中的那样识别和展开尾递归,从而大大简化递归并释放许多资源。
然而,一般来说,递归会占用大量堆栈和堆 space,这就是为什么使用递归并不总是一个好主意。
如果您想要进行垃圾回收,通过将变量设置为 null
是一种主动删除引用的好方法。但是,不要忘记对象可能包含事件注册、独立线程等等,所以如果你想对一个对象进行垃圾回收,你必须确保它们的引用没有泄漏。 Object
不是这种情况,但其他 类 的实例可能会发生这种情况。至于这个问题,我相信除非你主动摆脱它的引用,否则它不会被垃圾收集,但我没有用所有 Java 版本测试过这个。
我发现引用对象 的局部变量在不再需要时会被 垃圾回收。但是,这在调试时不适用。我想调试器会保留对它的引用,但在常规执行中不需要它。
尝试执行以下代码,看看会发生什么。
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
因此,即使本地方法仍在堆栈中,较早的引用也会被垃圾回收。
Is the object (i.e. 'a') in the first recursion marked for garbage collection once the second recursion begins or does it need to be explicitly marked null since the outer function never finishes because of the recursion.
答案是...它取决于平台。
在 doWork()
returns 的声明实例之前,变量 a
仍在范围内。另一方面,对我们来说很明显,一旦到达递归调用,a
变量就不会影响计算,并且(理论上)这意味着 GC 不再需要考虑它确定可达性的目的。
因此,归结为 JVM 是否足够聪明,可以意识到 a
不再重要。那是平台相关的。正如另一个答案所指出的,运行 附加的调试器可以改变这一点。
另一个答案提到了尾调用优化。当然,如果 JVM 确实实现了此优化,那么 "old" a
在概念上将被优化后的 "call" 中的 "new" 替换。但是,没有 HotSpot JVM(至少达到 Java 8)实现尾调用优化,因为优化会干扰依赖于能够计算堆栈帧的 Java 代码;例如安全码。
但是,如果此特定代码 是 严重的存储泄漏,那么它很可能没有实际意义,因为(缺少尾调用优化)代码可能会给您a WhosebugError
在它填满堆之前。
我知道在常规循环的每次迭代中,对象变得不可访问并被标记为垃圾回收。递归调用呢?类似于:
public void doWork() {
Object a = new Object();
....some work with a...
this.sleep(60000);
doWork();
}
第一次递归中的对象(即'a')是否在第二次递归开始后标记为垃圾回收,或者是否需要显式标记为空,因为外部函数由于递归而永远不会完成。
在每次递归调用期间,局部变量(此处引用 "a")被压入堆栈。局部变量是 GC 根。在第二次递归调用期间,新引用被压入堆栈。但是,第一个引用仍然存在,因此该对象仍然可以访问,因此不能被垃圾回收。
因此,如果您希望第一个创建的对象被标记为垃圾回收(在函数尚未完成时),您应该明确地将 "a" 设置为 null。
这里有一个有用的link理解GC:http://javabook.compuware.com/content/memory/how-garbage-collection-works.aspx
您应该假设只要方法本身在调用堆栈中,所有本地引用都是有效的。这意味着您每次递归都会获得一个对 a 的有效引用。
运行时可能会发现,当不再访问变量 a 的槽时,它可能会被覆盖。在那种情况下,即使 doWork() 尚未返回,它也变得无法访问。但是你不能依赖这个。在上次使用 a.
后立即帮助a = null;
理论上,递归是通过将实际的帧(局部变量集)压入栈中,并为新的执行打开一个新的帧来完成的。
堆中的对象仍然被压入堆栈的帧中的变量所引用,这意味着对于GC来说,对象仍然是"live"。
然而,许多编译器(我假设 javac 也是如此)能够像示例中的那样识别和展开尾递归,从而大大简化递归并释放许多资源。
然而,一般来说,递归会占用大量堆栈和堆 space,这就是为什么使用递归并不总是一个好主意。
如果您想要进行垃圾回收,通过将变量设置为 null
是一种主动删除引用的好方法。但是,不要忘记对象可能包含事件注册、独立线程等等,所以如果你想对一个对象进行垃圾回收,你必须确保它们的引用没有泄漏。 Object
不是这种情况,但其他 类 的实例可能会发生这种情况。至于这个问题,我相信除非你主动摆脱它的引用,否则它不会被垃圾收集,但我没有用所有 Java 版本测试过这个。
我发现引用对象 的局部变量在不再需要时会被 垃圾回收。但是,这在调试时不适用。我想调试器会保留对它的引用,但在常规执行中不需要它。
尝试执行以下代码,看看会发生什么。
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
因此,即使本地方法仍在堆栈中,较早的引用也会被垃圾回收。
Is the object (i.e. 'a') in the first recursion marked for garbage collection once the second recursion begins or does it need to be explicitly marked null since the outer function never finishes because of the recursion.
答案是...它取决于平台。
在 doWork()
returns 的声明实例之前,变量 a
仍在范围内。另一方面,对我们来说很明显,一旦到达递归调用,a
变量就不会影响计算,并且(理论上)这意味着 GC 不再需要考虑它确定可达性的目的。
因此,归结为 JVM 是否足够聪明,可以意识到 a
不再重要。那是平台相关的。正如另一个答案所指出的,运行 附加的调试器可以改变这一点。
另一个答案提到了尾调用优化。当然,如果 JVM 确实实现了此优化,那么 "old" a
在概念上将被优化后的 "call" 中的 "new" 替换。但是,没有 HotSpot JVM(至少达到 Java 8)实现尾调用优化,因为优化会干扰依赖于能够计算堆栈帧的 Java 代码;例如安全码。
但是,如果此特定代码 是 严重的存储泄漏,那么它很可能没有实际意义,因为(缺少尾调用优化)代码可能会给您a WhosebugError
在它填满堆之前。