Java可以在对象仍在范围内时完成它吗?

2022-09-01 14:19:31

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

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();
   }
}

我认为正在发生的事情是,在完成器线程的第二行之后被检测为没有引用,并且由终结器线程进行GC和最终确定 - 当循环仍在发生时,使用while仍在“范围内”。amain()forba

这合理吗?Java是否允许在对象超出范围之前对其进行GC?

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


答案 1

Java 能否在对象仍在作用域中时完成该对象?

是的。

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

来自ajb的答案几乎通过引用JLS中的一个重要段落(+1)得到了它。但是,我不认为它直接适用于这种情况。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 上,每次都会完成此操作。如果在最后取消注释,将永远不会最终确定。aprintlna

注释掉后,可以看到可访问性规则如何应用。当代码到达循环时,线程不可能访问 。因此,它是无法访问的,因此需要完成和垃圾回收。printlna

请注意,该名称仍在作用域中,因为可以从其声明到块的末尾使用封闭块中的任何位置(在本例中为方法主体)。确切的范围规则在 JLS §6.3 中介绍。但实际上,正如您所看到的,范围与可访问性或垃圾回收无关。aamain

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


答案 2

JLS §12.6.1

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

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

可能的(丑陋的)解决方法:添加到主类或其他一些类,然后在 末尾添加或引用的其他内容。我不认为优化器可以确定从未设置过的内容(因为某些类总是可以使用反射来设置它);因此,它将无法判断不再需要它。至少,这种“解决方法”可以用来确定这是否确实是问题所在。public static boolean alwaysFalse = false;main()if (alwaysFalse) System.out.println(a);aalwaysFalsea