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

时间:2014-06-24 00:53:34

标签: java garbage-collection

我一直在调查我的代码中的一个错误,这个错误似乎是由一些"丑陋"终结者代码。代码看起来大致像这样

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 1}}循环仍在发生,使用a而{{1}}仍在"范围内"。

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

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

2 个答案:

答案 0 :(得分:21)

  

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

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

answer from 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上,这将每次最终确定a。如果您在结尾处取消注释println,则a永远不会最终确定。

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

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

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

答案 1 :(得分:7)

JLS §12.6.1

  

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

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

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