java可以在对象仍在范围内时对其进行最终确定吗?
我一直在研究我的代码中的一个错误,该错误似乎是由一些丑陋"的终结器代码引起的.代码大致是这样的
I've been looking into a bug in my code that seems to be caused by some "ugly" finalizer code. The code looks roughly like this
public class A {
public B b = new B();
@Override public void finalize() {
b.close();
}
}
public class B {
public void close() { /* do clean up our resources. */ }
public void doSomething() { /* do something that requires us not to be closed */ }
}
void main() {
A a = new A();
B b = a.b;
for(/*lots of time*/) {
b.doSomething();
}
}
我认为正在发生的是 a
被检测为在 main()
的第二行之后没有引用,并被终结器进行 GC 和终结线程 - 当 for
循环仍在发生时,使用 b
而 a
仍然在范围内".
What I think is happening is that a
is getting detected as having no references after the second line of main()
and getting GC'd and finalized by the finalizer thread - while the for
loop is still happening, using b
while a
is still "in scope".
这合理吗?java是否允许在对象超出范围之前对其进行GC?
Is this plausable? Is java allowed to GC an object before it goes out of scope?
注意:我知道在终结器中做任何事情都是不好的.这是我继承并打算修复的代码 - 问题是我是否正确理解了根本问题.如果这是不可能的,那么更微妙的东西一定是我的错误的根源.
Note: I know that doing anything inside finalizers is bad. This is code I've inherited and am intending to fix - the question is whether I'm understanding the root issue correctly. If this is impossible then something more subtle must be the root of my bug.
推荐答案
当一个对象还在作用域内时,Java 可以终结它吗?
Can Java finalize an object when it is still in scope?
是的.
但是,我在这里很迂腐.范围 是一个语言概念,它决定了名称的有效性.一个对象是否可以被垃圾回收(从而最终确定)取决于它是否可达.
However, I'm being pedantic here. Scope is a language concept that determines the validity of names. Whether an object can be garbage collected (and therefore finalized) depends on whether it is reachable.
ajb 的回答引用了 JLS 的一段重要段落,几乎得到了答案 (+1).但是我不认为它直接适用于这种情况.JLS §12.6.1 还说:
The answer from ajb almost had it (+1) by citing a significant passage from the JLS. However I don't think it's directly applicable to the situation. JLS §12.6.1 also says:
可达对象是可以在任何潜在的持续计算中从任何活动线程访问的任何对象.
A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
现在考虑将其应用于以下代码:
Now consider this applied to the following code:
class A {
@Override protected void finalize() {
System.out.println(this + " was finalized!");
}
public static void main(String[] args) {
A a = new A();
System.out.println("Created " + a);
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
// System.out.println(a + " was still alive.");
}
}
在 JDK 8 GA 上,这将每次完成 a
.如果您在最后取消注释 println
,则 a
将永远不会被最终确定.
On JDK 8 GA, this will finalize a
every single time. If you uncomment the println
at the end, a
will never be finalized.
将 println
注释掉后,可以看到可达性规则是如何应用的.当代码到达循环时,线程不可能访问a
.因此它是不可达的,因此需要完成和垃圾回收.
With the println
commented out, one can see how the reachability rule applies. When the code reaches the loop, there is no possible way that the thread can have any access to a
. Thus it is unreachable and is therefore subject to finalization and garbage collection.
请注意,名称 a
仍然在 范围内,因为可以在封闭块内的任何地方使用 a
—— 在这种情况下, main
方法体——从它的声明到块的结尾.确切的范围规则在 JLS §6.3.但实际上,正如您所见,范围与可达性或垃圾收集无关.
Note that the name a
is still in scope because one can use a
anywhere within the enclosing block -- in this case the main
method body -- from its declaration to the end of the block. The exact scope rules are covered in JLS §6.3. But really, as you can see, scope has nothing to do with reachability or garbage collection.
为了防止对象被垃圾回收,您可以在静态字段中存储对它的引用,或者如果您不想这样做,您可以通过稍后在同一方法中使用它来保持它的可访问性耗时的循环.在其上调用像 toString
这样的无害方法就足够了.
To prevent the object from being garbage collected, you can store a reference to it in a static field, or if you don't want to do that, you can keep it reachable by using it later on in the same method after the time-consuming loop. It should be sufficient to call an innocuous method like toString
on it.
相关文章