当java仍然在范围内时,java能否最终确定它?

我一直在研究我的代码中的一个错误,似乎是由一些“丑陋的”终结器代码引起的。 代码看起来大致如此

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 = ab; for(/*lots of time*/) { b.doSomething(); } } 

我认为发生的事情是a被检测为在main()的第二行之后没有引用并且获得GC并且最终确定了终结器线程 – 而for循环仍在发生,使用ba仍然是“in范围”。

这有可能吗? 是否允许java在对象超出范围之前将其作为对象?

注意:我知道在终结器中做任何事情都很糟糕。 这是我inheritance并打算修复的代码 – 问题是我是否正确理解了根本问题。 如果这是不可能的,那么更微妙的东西必定是我的bug的根源。

当Java仍在范围内时,Java能否最终确定它?

是。

但是,我在这里很迂腐。 范围是一种确定名称有效性的语言概念。 对象是否可以被垃圾收集(并因此最终确定)取决于它是否可达

ajb的回答几乎得到了它(+1)引用了JLS的重要段落。 但我不认为它直接适用于这种情况。 JLS§12.6.1也说:

可到达对象是可以在任何活动线程的任何潜在持续计算中访问的任何对象。

现在考虑将此应用于以下代码:

 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上,这将每次都完成。 如果您在最后取消注释println ,则永远不会最终确定。

随着println注释,可以看到可达性规则是如何应用的。 当代码到达循环时,线程无法访问a 。 因此它无法访问,因此需要进行最终确定和垃圾收集。

请注意,名称a仍然在范围内,因为可以使用封闭块中a任何位置 - 在本例中是main方法体 - 从其声明到块的末尾。 JLS§6.3中涵盖了确切的范围规则。 但实际上,正如您所看到的,范围与可达性或垃圾收集无关。

为了防止对象被垃圾收集,您可以在静态字段中存储对它的引用,或者如果不想这样做,您可以稍后在相同的方法中使用它来保持它可访问循环。 调用像toString这样的无害方法就足够了。

JLS§12.6.1 :

可以设计优化程序的转换,以减少可达到的对象的数量,使其少于可以被认为可达的对象的数量。 例如,Java编译器或代码生成器可以选择设置将不再用于null的变量或参数,以使得此类对象的存储可能更快地被回收。

所以是的,我认为允许编译器添加隐藏代码以将a设置为null ,从而允许它被垃圾收集。 如果发生了这种情况,您可能无法从字节码中判断出来(请参阅@ user2357112的评论)。

可能(丑陋)的解决方法:添加public static boolean alwaysFalse = false; 到main类或其他类,然后在main()的末尾添加if (alwaysFalse) System.out.println(a); 或其他引用a东西。 我不认为优化器可以确定永远不会设置alwaysFalse (因为某些类总是可以使用reflection来设置它); 因此,它无法分辨出不再需要a。 至少,这种“解决方法”可用于确定这是否确实是问题。