Java终结器和垃圾回收机制

4

现在是时候质疑JAVA System.GC()和System.runFinalizer了。

public interface SomeAction {
    public void doAction();
}

public class SomePublisher {
    private List<SomeAction> actions = new ArrayList<SomeAction>();

    public void subscribe(SomeSubscriber subscriber) {
        actions.add(subscriber.getAction());
    }
}

public class SomeSubscriber {
    public static int Count;

    public SomeSubscriber(SomePublisher publisher) {
        publisher.subscribe(this);
    }

    public SomeAction getAction() {
        final SomeSubscriber me = this;
        class Action implements SomeAction {

            @Override
            public void doAction() {
               me.doSomething();
            }
        }

        return new Action();
    }

    @Override
    protected void finalize() throws Throwable {
        SomeSubscriber.Count++;
    }

    private void doSomething() {
        // TODO: something
    }
}

现在我正在尝试在主代码块中强制进行垃圾回收和终结器。

 SomePublisher publisher = new SomePublisher();

        for (int i = 0; i < 10; i++) {
            SomeSubscriber subscriber = new SomeSubscriber(publisher);
            subscriber = null;
        }

        System.gc();
        System.runFinalization();

        System.out. println("The answer is: " + SomeSubscriber.Count);

由于JAVA GC调用并不保证被调用(正如javadoc所解释的和Java中finalize()方法何时被调用?所解释的),我最初的想法是它会输出随机SomeSubscriber.Count。(至少通过System.GC和finalizer强制输出1。)
然而,它总是为0。
有人能解释一下这种行为吗?
(此外,静态成员字段是否独立于类实例存在,并且在代码执行期间永远不会被销毁?)

1
如果你不知道静态字段是什么,那么你就不应该担心终结器或垃圾回收。 - Kayaman
静态字段是类变量,存在于没有实例化类的情况下,被类实例所共享。 - J. Doe
你没听懂“不保证被调用”的哪一部分? - Kayaman
你不应该显式地调用 System.gc()。在最好的情况下,它将什么也不会做,在最坏的情况下,它将始终导致全停顿垃圾回收(例如,当使用CMS收集器时)。 - Mick Mnemonic
如果您查看来自https://dev59.com/anE95IYBdhLWcg3wAo9U的techloris_109示例,可以手动调用System.GC()并在将实例置空后运行finalize块。为什么在我的情况下不会发生同样的事情呢? - J. Doe
1个回答

2
你的测试有一个缺陷 - 即使假设调用System.gc()System.runFinalization()实际上会运行GC和终结器,你创建的实例不是垃圾收集的候选对象,因此不会被终结也不会被回收。 你运行这行代码10次:
SomeSubscriber subscriber = new SomeSubscriber(publisher);

这会调用SomeSubscriber的构造函数,该构造函数表示:
publisher.subscribe(this);

因此,发布者对象被赋予对当前正在构建的对象的引用。它会对其进行什么操作?
actions.add(subscriber.getAction());

好的,它调用订阅者的getAction()方法并存储结果。那么getAction()方法是做什么的?

public SomeAction getAction() {
    final SomeSubscriber me = this;
    class Action implements SomeAction {

        @Override
        public void doAction() {
           me.doSomething();
        }
    }

    return new Action();
}

它创建了一个本地类的实例。该实例持有封闭的SomeSubscriber对象的实例。事实上,它有两个这样的实例 - me和每个内部类都有的隐式引用到封闭实例。本地类是内部类!
因此,当您将操作列表存储在publisher实例中时,还将其中所有订阅者的引用存储在其中。当您运行System.gc()System.runFinalization()时,publisher实例仍然活着,因此这些引用仍然活着,因此您的任何SomeSubscriber实例实际上都不符合垃圾收集条件。 确保您还将publisher = null赋值,并且也许您将能够看到最终运行。我还建议将Count声明为volatile(并且不要将其称为Count而应该是count - 变量应以小写字母开头),因为finalizer通常在不同的线程中运行。

就是这样!问题不在于 GC 而在于本地类的引用! - J. Doe
即使您解决了引用问题,也不能保证finalize()方法的执行。请参考:https://dev59.com/JWEi5IYBdhLWcg3w6f6e#34472233 - Ravindra babu
@ravindra 这是对的,而且这些测试通常取得一定程度上的成功并且是一个重要的学习工具。然而,做好它们非常重要。 - RealSkeptic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接