题
我已经阅读了很多 Java 新手问题 finalize()
令人困惑的是,没有人真正明确表示 Finalize() 是一种不可靠的清理资源的方法。我看到有人评论说他们用它来清理连接,这真的很可怕,因为接近保证连接关闭的唯一方法是最后实现 try (catch)。
我没有接受过 CS 教育,但我已经用 Java 专业编程近十年了,而且我从未见过有人实现过 finalize()
在生产系统中。这仍然并不意味着它没有用处,也不意味着与我共事的人一直在做正确的事情。
所以我的问题是,有哪些用例可以实现 finalize()
无法通过语言中的另一个过程或语法更可靠地处理?
请提供具体的场景或您的经验,简单地重复Java教科书,或最终确定的预期用途是不够的,因为不是这个问题的意图。
解决方案
您可以将其用作持有外部资源(套接字、文件等)的对象的后盾。实施一个 close()
需要调用的方法和文档。
实施 finalize()
做 close()
如果您发现尚未完成,则进行处理。也许有东西倾倒到 stderr
指出您正在清理一个有问题的来电者。
它在特殊/有缺陷的情况下提供额外的安全性。并非每个来电者都会做正确的事情 try {} finally {}
每次都有东西。不幸的是,但在大多数环境下都是如此。
我同意很少需要它。正如评论者所指出的,它会带来 GC 开销。仅当您在长时间运行的应用程序中需要“腰带和吊带”安全时才使用。
我看到从 Java 9 开始, Object.finalize()
已弃用!他们向我们指出 java.lang.ref.Cleaner
和 java.lang.ref.PhantomReference
作为替代方案。
其他提示
finalize()
向 JVM 暗示,在未指定的时间执行代码可能会更好。当您希望代码神秘地无法运行时,这非常有用。
在终结器中做任何重要的事情(基本上除了日志记录之外的任何事情)在三种情况下也很好:
- 您想赌其他最终对象仍将处于程序其余部分认为有效的状态。
- 您想要向所有具有终结器的类的所有方法添加大量检查代码,以确保它们在终结后行为正确。
- 您想要意外地复活最终确定的对象,并花费大量时间试图找出它们不起作用的原因,和/或为什么它们在最终发布时没有最终确定。
如果您认为需要 Finalize(),有时您真正想要的是 幻象参考 (在给出的示例中,它可以保存对其所引用对象使用的连接的硬引用,并在虚拟引用排队后将其关闭)。这还有一个属性,它可能神秘地永远不会运行,但至少它不能调用或复活最终对象的方法。因此,它非常适合以下情况:您绝对不需要完全关闭该连接,但您很想这样做,并且您的班级的客户不能或不会自己调用关闭(这实际上是足够公平的 -如果你设计的接口是这样的,那么拥有垃圾收集器还有什么意义呢? 要求 收集前应采取哪些具体行动?这让我们回到了 malloc/free 的时代。)
其他时候,您需要您认为自己正在设法变得更加强大的资源。例如,为什么需要关闭该连接?它最终必须基于系统提供的某种I/O(套接字、文件,等等),那么为什么不能在最低级别的资源被 gced 时依赖系统为您关闭它呢?如果另一端的服务器绝对要求您彻底关闭连接而不是仅仅断开套接字,那么当有人被运行代码的机器的电源线绊倒或中间网络中断时会发生什么?
免责声明:我过去从事过 JVM 实现的工作。我讨厌终结器。
一个简单的规则:永远不要使用终结器。
仅对象具有终结器这一事实(无论它执行什么代码)就足以导致垃圾收集的相当大的开销。
来自一个 文章 作者:布赖恩·戈茨:
与没有最终化器的对象相比,具有最终变化器的对象(具有非平凡最终确定()方法的对象)具有明显的间接费用,应谨慎使用。最终定量的对象既分配较慢又较慢。在分配时间,JVM必须在垃圾收集器中注册任何最终确定对象,并且(至少在热点JVM实现中)最终确定对象必须遵循比大多数其他对象较慢的分配路径。同样,最终定义的对象也较慢收集。在回收最终定义的对象之前,它至少需要两个垃圾收集周期(在最好的情况下),并且垃圾收集器必须做额外的工作以调用最终制度。结果是花费更多的时间分配和收集对象以及对垃圾收集器的压力更大,因为无法实现的最终定量对象使用的内存更长。结合一个事实,即不能保证在任何可预测的时间范围内甚至根本无法运行结局,并且您可以看到,最少的最终化是正确使用的工具。
我在生产代码中使用 Finalize 的唯一一次是检查给定对象的资源是否已清理,如果没有,则记录一条非常明确的消息。它实际上并没有尝试自己做这件事,只是如果做得不好它就会大喊大叫。事实证明非常有用。
我从1998年就开始专业做Java了,从来没有实现过 finalize()
. 。不止一次。
我不确定你能从中得到什么,但是......
itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41
所以我猜太阳发现了 一些 (他们认为)应该使用它的情况。
接受的答案很好,我只是想补充一点,现在有一种方法可以在不实际使用它的情况下获得 Finalize 功能。
查看“参考”课程。弱引用、虚引用和软引用。
您可以使用它们来保留对所有对象的引用,但仅此引用不会停止 GC。这样做的巧妙之处在于,您可以让它在删除时调用一个方法,并且该方法可以 保证 被称为。
至于最终确定:我使用过一次 Finalize 来了解哪些对象被释放。您可以使用静态、引用计数等玩一些巧妙的游戏 - 但这仅用于分析,但要注意这样的代码(不仅在 Finalize 中,而且这是您最有可能看到它的地方):
public void finalize() {
ref1 = null;
ref2 = null;
othercrap = null;
}
这表明有人不知道自己在做什么。实际上从来不需要像这样的“清理”。当类被 GC 时,这是自动完成的。
如果你在finalize中发现这样的代码,那么编写它的人肯定会感到困惑。
如果它在其他地方,则该代码可能是对错误模型的有效补丁(一个类会保留很长时间,并且由于某种原因,在对象被 GC 之前必须手动释放它引用的东西)。一般来说,这是因为有人忘记删除监听器或其他东西,并且无法弄清楚为什么他们的对象没有被 GC,所以他们只是删除它引用的东西,然后耸耸肩走开。
它不应该被用来“更快”地清理东西。
class MyObject {
Test main;
public MyObject(Test t) {
main = t;
}
protected void finalize() {
main.ref = this; // let instance become reachable again
System.out.println("This is finalize"); //test finalize run only once
}
}
class Test {
MyObject ref;
public static void main(String[] args) {
Test test = new Test();
test.ref = new MyObject(test);
test.ref = null; //MyObject become unreachable,finalize will be invoked
System.gc();
if (test.ref != null) System.out.println("MyObject still alive!");
}
}
====================================
结果:
This is finalize
MyObject still alive!
=====================================
因此,您可以在 Finalize 方法中使无法访问的实例变得可访问。
finalize()
对于捕获资源泄漏很有用。如果资源应该关闭但没有将其未关闭的事实写入日志文件并关闭它。这样您就可以消除资源泄漏,并让自己知道它已经发生,以便您可以修复它。
我从 1.0 alpha 3 (1995) 就开始用 Java 编程,但我还没有重写任何东西的 Finalize...
您不应该依赖 Finalize() 来清理您的资源。Finalize() 在类被垃圾回收之前不会运行(如果那时)。当您使用完资源后,最好显式释放它们。
强调上述答案中的一点:终结器将在单独的 GC 线程上执行。我听说过一个主要的 Sun 演示,开发人员在一些终结器中添加了一个小睡眠,并故意使原本精美的 3D 演示崩溃。
最好避免,但测试环境诊断可能除外。
Eckel的Java思想有 一个很好的部分 关于这一点。
嗯,我曾经用它来清理没有返回到现有池的对象。
它们被多次传递,因此无法判断它们何时可以安全返回泳池。问题在于,它在垃圾收集期间带来了巨大的损失,远远大于通过对象池节省的任何费用。它已经制作了大约一个月,然后我拆掉了整个池,使一切变得动态并完成了。
小心你在一个地方所做的事情 finalize()
. 。特别是如果您将它用于调用 close() 等操作以确保资源得到清理。我们遇到过几种情况,其中我们将 JNI 库链接到正在运行的 java 代码,并且在任何使用 Finalize() 调用 JNI 方法的情况下,我们都会遇到非常严重的 java 堆损坏。损坏不是由底层 JNI 代码本身引起的,本机库中的所有内存跟踪都很好。事实上,我们根本就是从 Finalize() 调用 JNI 方法。
这是在 JDK 1.5 中进行的,该版本仍在广泛使用。
直到很久以后我们才发现出了问题,但最终罪魁祸首始终是使用 JNI 调用的 Finalize() 方法。
当编写供其他开发人员使用的代码时,需要调用某种“清理”方法来释放资源。有时,其他开发人员忘记调用您的清理(或关闭、销毁或其他)方法。为了避免可能的资源泄漏,您可以检查 Finalize 方法以确保该方法被调用,如果没有被调用,您可以自己调用它。
许多数据库驱动程序在其 Statement 和 Connection 实现中执行此操作,以便为忘记调用 close 的开发人员提供一点安全性。
编辑:好吧,确实不行。我实现了它,并认为如果它有时失败对我来说没关系,但它甚至没有调用一次 Finalize 方法。
我不是专业程序员,但在我的程序中,我认为这是使用 Finalize() 的一个很好的例子,即一个缓存,它在销毁之前将其内容写入磁盘。因为没有必要每次销毁时都执行它,它只会加速我的程序,我希望我没有做错。
@Override
public void finalize()
{
try {saveCache();} catch (Exception e) {e.printStackTrace();}
}
public void saveCache() throws FileNotFoundException, IOException
{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
out.writeObject(cache);
}
删除已添加到全局/静态位置(不需要)并且需要在删除对象时删除的内容可以很方便。例如:
private void addGlobalClickListener() { weakAwtEventListener = new WeakAWTEventListener(this); Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK); } @Override protected void finalize() throws Throwable { super.finalize(); if(weakAwtEventListener != null) { Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener); } }
iirc - 您可以使用 Finalize 方法作为为昂贵的资源实现池机制的一种手段 - 这样它们也不会得到 GC。
作为旁注:
覆盖 Finalize() 的对象会被垃圾收集器特殊对待。通常,当对象不再处于范围内时,该对象会在收集周期中立即销毁。然而,可终结的对象会被移动到队列中,其中单独的终结线程将排空队列并在每个对象上运行 Finalize() 方法。一旦finalize()方法终止,该对象最终将准备好在下一个周期进行垃圾回收。
一旦我们使用完资源(文件、套接字、流等),就需要关闭它们。他们一般有 close()
我们一般调用的方法 finally
的部分 try-catch
声明。有时 finalize()
也可以被少数开发人员使用,但在我看来这不是一个合适的方式,因为不能保证 Finalize 总是被调用。
在 Java 7 中我们有 尝试资源 可以这样使用的语句:
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
// Processing and other logic here.
} catch (Exception e) {
// log exception
} finally {
// Just in case we need to do some stuff here.
}
在上面的例子中,try-with-resource会自动关闭资源 BufferedReader
通过调用 close()
方法。如果我们愿意,我们也可以实施 可关闭 在我们自己的课程中并以类似的方式使用它。IMO 看起来更简洁、更容易理解。
就我个人而言,我几乎从未使用过 finalize()
除非在一种罕见的情况下:我制作了一个自定义泛型类型集合,并编写了一个自定义 finalize()
执行以下操作的方法:
public void finalize() throws Throwable {
super.finalize();
if (destructiveFinalize) {
T item;
for (int i = 0, l = length(); i < l; i++) {
item = get(i);
if (item == null) {
continue;
}
if (item instanceof Window) {
((Window) get(i)).dispose();
}
if (item instanceof CompleteObject) {
((CompleteObject) get(i)).finalize();
}
set(i, null);
}
}
}
(CompleteObject
是我制作的一个接口,可以让您指定您已经实现了很少实现的 Object
方法如 #finalize()
, #hashCode()
, , 和 #clone()
)
所以,利用姐姐 #setDestructivelyFinalizes(boolean)
方法,使用我的集合的程序可以(帮助)保证销毁对此集合的引用也会销毁对其内容的引用,并处置任何可能无意中使 JVM 保持活动状态的窗口。我也考虑停止任何线程,但这引发了一个全新的蠕虫问题。
接受的答案列出了可以在最终确定期间关闭资源。
然而 这个答案 表明至少在使用 JIT 编译器的 java8 中,您会遇到意想不到的问题,有时甚至在您完成从对象维护的流中读取数据之前就调用终结器。
所以即使在这种情况下调用 Finalize 也会 不是 被推荐。