【问题标题】:Can java finalize an object when it is still in scope?java可以在对象仍在范围内时对其进行最终确定吗?
【发布时间】:2014-08-14 03:12:05
【问题描述】:

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

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

我认为正在发生的事情是amain() 的第二行之后被检测为没有引用,并被终结器线程进行GC 和终结——而for 循环仍在发生,使用ba 仍然“在范围内”。

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

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

【问题讨论】:

  • 据我所知,如果垃圾收集器 GC 对范围内的对象进行 GC,那么它就有一个 严重 错误,因为它正在收集不是垃圾的对象。所以我猜它是别的东西,但我对这些东西知之甚少,所以我完全有可能遗漏了什么......
  • 您已将这些类命名为 Outer 和 Inner。 Inner 真的是 Outer 的 Inner 类吗?还是您只是表示 Outer 引用了 Inner (代码示例似乎显示了这一点)?这是一个重要的区别,因为如果是非静态内部类,则 Inner 将隐式引用 Outer。
  • 一点术语说明:scope 是该语言的词汇结构。它与对象的生命周期相关,但最终不同。生命周期由 reachability 控制:如果没有代码路径可以到达对象,则它有资格被收集。通常,由当前范围内的变量引用的对象被认为是可访问的,但并非必须如此。
  • 顺便说一句:在 Java 9 中,有一个明确的方法可以让对象在 sope 结束之前保持可访问性:download.java.net/java/jdk9/docs/api/java/lang/ref/…Reference.reachabilityFende(Object)
  • @awksp 不,语言/编译器的词法范围与实际可达性无关。一个对象可能不再被使用,即使它在范围内并因此被收集,另一方面,通常对象比范围更容易到达(通过停留在堆栈槽中)。但后者是一个不应该依赖的实现细节(内联和 EA 会改变代码的可达性效果)。

标签: java garbage-collection


【解决方案1】:

JLS §12.6.1:

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

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

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

【讨论】:

  • 你无法从字节码中分辨出来,因为转换可能发生在 JIT 优化中。
  • 旁注:注意它不是public static **final** boolean alwaysFalse,否则它将是一个编译时常量,因此在编译时内联(编译器不会为if 块,根据 JLS)
  • 优化器不需要识别alwaysFalse从不 设置的,它只需要知道它在A 的构造和测试,按程序顺序。由于变量未声明volatile,因此优化后的代码不需要注意其他线程进行的更新,无论是直接还是通过反射。但是,即使它被声明为volatile,我们也不应该编写代码来解决假定的优化器无能。
  • @Holger 是的,这就是为什么我在回答中包含“可能”和“丑陋”这两个词......
  • 在Java9的main末尾使用Reference.reachabilityFence(a)是可靠的,但是如果需要这样的东西仍然很丑。
【解决方案2】:

当一个对象还在作用域内时,Java 可以终结它吗?

是的。

但是,我在这里很迂腐。 范围 是一个语言概念,它决定了名称的有效性。一个对象是否可以被垃圾回收(从而最终确定)取决于它是否可访问

answer from 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 上,这将每次最终确定 a。如果你取消注释最后的printlna 将永远不会最终确定。

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

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

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

【讨论】:

  • 学究与我的要求完全一致 - 你对范围的使用与我的匹配,我的“a 被检测为没有引用”与你的“可达”匹配。
  • 好的,如果学究式匹配,那就更好了。在这项业务中学究气是值得的。 :-) 一些评论者(Daniel Pryden 除外)将范围与可达性混为一谈,或者认为在范围内意味着可达性。我什至听到熟悉这种现象的人说,局部变量在块中最后一次使用后超出范围,这当然不是真的。
  • toString() 声明引起了我的怀疑。由于toString() 实现通常没有副作用,如果不使用其结果并且 JLS §12.6.1 建议允许此类优化导致更早收集,则可以完全删除调用。进一步注意,在没有同步的情况下,即使使用示例打印语句中的结果,也不能保证更长的生命周期。如果没有 happens-before 关系,终结器线程可能会看到主线程操作的不同顺序,即在循环完成之前完成 toString() 调用。
  • @Holger 严格来说你是对的,toString() 不提供完全保证防止 GC。但是,它应该足以满足大多数用途。在 Java 9 中有一个新的 API 可以提供这样的保证:Reference.reachabilityFence
  • @RFST 它从来没有,现在也不能保证。特定错误报告解决的所有问题都是 for-each 循环的不可见变量,其中包含可能会阻止集合被垃圾收集的迭代器。正如这个答案所示,这通常不适用于优化代码。实际上,我认为这很奇怪,现在javac 在每个代码中插入了额外的指令,以解决单个非常具体的极端情况,而一般行为(悬空局部变量仍然可能存在)没有改变。
猜你喜欢
  • 2011-09-19
  • 1970-01-01
  • 2011-04-25
  • 2017-07-21
  • 1970-01-01
  • 2018-02-13
  • 2017-06-05
  • 2020-04-18
  • 1970-01-01
相关资源
最近更新 更多