Java垃圾回收机制如何调用finalize()方法?

6
据我所知,垃圾回收器从一些初始对象(堆栈、静态对象)开始递归遍历构建可达对象的图。然后将这些对象占用的内存标记为已使用,并假设所有其余内存都是空闲的。
但是如果这个“空闲”内存包含一个带有finalize方法的对象呢?垃圾回收器必须调用它,但我不知道它如何知道不再可达的对象。
我认为垃圾回收器可以在这些“可终结”对象还活着时跟踪它们。如果是这样,那么即使这些对象仍然存在,拥有可终结对象会使垃圾回收变得更加昂贵吗?

4个回答

6

考虑 参考 API

它提供了一些具有特殊语义的引用,即弱引用、软引用和幻象引用。还有另一种非public类型的特殊引用,用于需要终结的对象。

现在,当垃圾收集器遍历对象图并遇到这样一个特殊引用对象时,它不会将通过此引用可达的对象标记为强可达,而是使用特殊语义标记为可达。因此,如果一个对象只能通过终结器访问,引用将被加入队列,以便一个(或一个以上)终结器线程可以轮询队列并执行finalize()方法(不是垃圾收集器本身调用此方法)。

换句话说,在这里垃圾回收器永远不会处理完全无法访问的对象。为了将特殊语义应用于可达性,必须使“引用对象”可达,以便可以通过该引用访问到引用对象。在最终器可达性的情况下,当创建对象时会调用Finalizer.register,它会创建FinalReference的一个子类Finalizer的实例,然后在其构造函数中调用add()方法,将引用插入全局链接列表中。因此,所有这些FinalReference实例都可以通过该列表访问,直到实际进行终结。

由于这个FinalReference在对象实例化的时候就会被创建,即使对象尚未被收集,如果它的类声明了一个非平凡的finalize()方法,那么由于存在终结要求,就已经存在一些开销。

另一个问题是,被终结器线程处理的对象可被该线程访问,甚至可能逃逸,具体取决于finalize()方法的行为。但下一次,此对象变得不可达时,特殊的引用对象就不存在了,所以它可以像任何其他不可达对象一样对待。

只有当内存非常低,下一个垃圾回收必须更早地执行以最终回收该对象时,这才会成为性能问题。但在参考实现(也称为“HotSpot”或“OpenJDK”)中,并不会发生这种情况。事实上,在等待终结器队列中的对象处理期间,可能会出现OutOfMemoryError,其处理可能使更多的内存可回收。不能保证终结运行足够快以满足您的需求。这就是为什么不能依赖它的原因。


3
但是如果这个“自由”内存包含一个有finalize方法的对象呢?GC必须调用它,但我不知道它如何知道不再可达的对象。
假设我们使用CMS垃圾收集器。在第一阶段成功标记所有活动对象后,它将再次扫描内存并删除所有死亡对象。GC线程不会直接为这些对象调用finalize方法。
在创建期间,它们被JVM包装并添加到finalizer队列中(请参见java.lang.ref.Finalizer.register(Object))。该队列在另一个线程(java.lang.ref.Finalizer.FinalizerThread)中处理,当没有对对象的引用时将调用finalize方法。更多细节请参见本博客文章。
那么,即使这些对象仍然存在,拥有finalizable对象是否会使垃圾收集变得更加昂贵?
正如您现在所看到的,大多数情况下不会。

但是垃圾回收器如何找到无法访问的对象呢?据我所知,它只“看到”可访问的对象,其余所有对象都是“自由”的内存。如果没有对特定的无法访问的对象的引用,GC如何区分它呢? - Vlad Shevchenko
1
当具有非平凡 finalize() 方法的对象被创建时,会调用 Finalizer.register(Object)。以这种方式创建的 FinalReference 的存在确保它可以在以后入队。 - Holger
@VladShevchenko GC 会标记活着的对象。Java 中的每个对象都有一些保留的空间用于元信息,例如它是否可达,它经历了多少次垃圾回收等。然后,GC 扫描堆并删除未被标记的对象。 - AdamSkywalker

-1

finalise 方法在对象即将被垃圾回收时被调用。也就是说,当 GC 确定该对象不再被引用时,它可以调用该对象的 finalise 方法。GC 不必跟踪要进行终结的对象。


-1
根据 javadocfinalize 会在垃圾收集器确定对象没有更多的引用时调用。
因此,该决定基于引用计数或类似的东西。
实际上,可能根本不会调用此方法。因此,将其用作析构函数可能不是一个好主意。

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