为什么要实现finalize()?
我已经阅读了许多关于finalize()的新手Java问题,发现令人困惑的是,没有人真正明确地说finalize()是清理资源的不可靠方法。我看到有人评论说他们用它来清理Connections,这确实很吓人,因为要保证关闭Connection的唯一方法就是最终实现try(catch)。
我当时没有在CS上学,但是近十年来我一直在用Java专业编程,而且我从未见过有人在生产系统中实现finalize()。这仍然并不意味着它没有其用途,或者与我一起工作的人都在正确地做它。
所以我的问题是,有什么用例可以实现finalize(),而不能通过语言中的其他过程或者语法更可靠地进行处理?
请提供特定的方案或者经验,仅仅重复一本Java教科书,或者最终确定其预期的用途是不够的,这不是此问题的目的。
解决方案
自1998年以来,我一直从事Java专业工作,并且从未实现finalize()。不止一次。
编写将由其他开发人员使用的代码时,需要调用某种"清理"方法以释放资源。有时那些其他开发人员忘记调用清理(或者关闭或者销毁,或者其他方式)方法。为了避免可能的资源泄漏,我们可以签入finalize方法以确保调用了该方法,如果不是,则可以自己调用它。
许多数据库驱动程序在其Statement和Connection实现中执行此操作,以为忘记忘记关闭它们的开发人员提供一点安全。
我们不应该依赖finalize()为我们清理资源。如果是的话,finalize()直到类被垃圾回收后才会运行。使用完资源后,显式释放资源会更好。
我们可以将其用作保存外部资源(套接字,文件等)的对象的支持。实现close()
方法并记录需要调用的方法。
如果发现未完成,则实现finalize()以进行close()处理。也许是将一些东西丢给了stderr,以指出我们是在有错误的呼叫者之后进行清理。
在特殊/越野情况下,它提供了额外的安全性。并非每个调用者每次都将做正确的try {}最终{}
东西。不幸的是,但在大多数环境中都是如此。
我同意几乎不需要它。正如评论者所指出的那样,它带有GC开销。仅在需要长时间运行的应用程序中使用"皮带和吊带"安全性时才使用。
我看到从Java 9开始,不赞成使用Object.finalize()!他们将我们指向" java.lang.ref.Cleaner"和" java.lang.ref.PhantomReference"作为替代。
我在生产代码中使用finalize的唯一一次是执行检查,以清除给定对象的资源,如果没有,则记录一条非常明确的消息。它实际上并没有尝试自己做,如果做得不好,它只会大声喊叫。原来是相当有用的。
在finalize()中要小心。尤其是在将其用于调用close()以确保资源被清理之类的情况下。我们遇到了将JNI库链接到正在运行的Java代码的几种情况,并且在任何情况下使用finalize()调用JNI方法的情况,都将导致非常严重的Java堆损坏。损坏不是由基础JNI代码本身引起的,所有内存跟踪在本机库中都很好。完全是事实,我们根本没有从finalize()调用JNI方法。
这是与仍在广泛使用的JDK 1.5一起使用的。
我们要等到很久以后才发现出了问题,但最终的罪魁祸首始终是利用JNI调用的finalize()方法。
finalize()向JVM暗示在未指定的时间执行代码可能会更好。当我们希望代码神秘地无法运行时,这很好。
在以下三种情况下,在终结器中做任何重要的事情(基本上是除日志记录之外的其他事情)也很不错:
- 我们想赌博其他定型对象仍将处于程序其余部分认为有效的状态。
- 我们想要向具有终结器的所有类的所有方法中添加大量检查代码,以确保它们在终结后的行为正确。
- 我们想意外地复活已完成的对象,并花费大量时间试图弄清为什么它们不起作用,和/或者为什么最终发布它们时未最终确定它们。
如果我们认为需要finalize(),有时我们真正想要的是幻像引用(在给定的示例中,该幻像引用可以保存对由其Referand使用的连接的硬引用,并在将幻像引用排队后将其关闭)。它也具有它可能神秘地永远不会运行的特性,但是至少它不能在终结的对象上调用方法或者复活终结的对象。因此,在我们绝对不需要干净地关闭该连接,但我们确实想这样做的情况下,这是正确的选择,而班级的客户却不能或者不会调用自己的关闭(实际上这很公平如果我们设计的接口需要在收集之前采取特定的措施,那么根本就没有垃圾收集器是什么意思呢?这让我们回到了malloc / free的时代。
其他时候,我们需要我们认为自己正在设法变得更强大的资源。例如,为什么需要关闭该连接?它最终必须基于系统提供的某种I / O(套接字,文件等),所以为什么当最低资源级别获得使用时,为什么不能依靠系统为我们关闭系统呢?如果另一端的服务器绝对要求我们干净地关闭连接而不是仅仅断开插座,那么当有人在运行代码的计算机的电源电缆上跳闸或者中间网络断开时,会发生什么?
免责声明:过去我一直在从事JVM实现。我讨厌终结者。
嗯,我曾经用它来清理没有返回到现有池的对象。它们经过了很多次,因此无法确定何时可以安全地将其送回泳池。问题在于,它在垃圾回收期间造成了巨大的损失,这远大于通过合并对象而节省的任何费用。在我删除整个池,使所有内容变得动态并完成之后,它已经投入生产了大约一个月。
我曾经使用finalize来了解释放了哪些对象。我们可以使用静态函数,引用计数等进行一些巧妙的游戏-但这仅用于分析。
公认的答案是好的,我只是想补充一点,现在有一种方法可以使finalize功能完全不实际使用。
查看"参考"类。参考不足等
我们可以使用它们保留对所有对象的引用,但是该引用ALONE不会停止GC。整洁的事情是,我们可以在删除该方法时让它调用一个方法,并且可以保证该方法被调用。
需要注意的另一件事。每当我们在代码中的任何地方看到类似这样的内容时(不仅是完成过程,而且我们最有可能看到的地方):
public void finalize() { ref1 = null; ref2 = null; othercrap = null; }
这表明有人不知道自己在做什么。几乎不需要这样的"清理"。类经过GC处理后,这是自动完成的。
如果在finalize中找到这样的代码,则可以确保编写该代码的人感到困惑。
如果在其他地方,则可能是该代码是错误模型的有效补丁(类保留了很长时间,并且由于某种原因,它引用的内容必须在对象进行GC处理之前手动释放)。通常,这是因为某人忘记删除一个监听器或者其他东西,并且无法弄清楚为什么不对他们的对象进行GC处理,因此他们只是删除了它所指的内容,耸了耸肩然后走开了。
切勿使用它来清理"更快"的东西。
一个简单的规则:永远不要使用终结器。仅对象具有终结器(无论它执行什么代码)的事实就足以导致垃圾回收的相当大的开销。
摘自Brian Goetz的文章:
Objects with finalizers (those that have a non-trivial finalize() method) have significant overhead compared to objects without finalizers, and should be used sparingly. Finalizeable objects are both slower to allocate and slower to collect. At allocation time, the JVM must register any finalizeable objects with the garbage collector, and (at least in the HotSpot JVM implementation) finalizeable objects must follow a slower allocation path than most other objects. Similarly, finalizeable objects are slower to collect, too. It takes at least two garbage collection cycles (in the best case) before a finalizeable object can be reclaimed, and the garbage collector has to do extra work to invoke the finalizer. The result is more time spent allocating and collecting objects and more pressure on the garbage collector, because the memory used by unreachable finalizeable objects is retained longer. Combine that with the fact that finalizers are not guaranteed to run in any predictable timeframe, or even at all, and you can see that there are relatively few situations for which finalization is the right tool to use.
为了突出上述答案中的一点:终结器将在单独的GC线程上执行。我听说过一个主要的Sun演示,其中开发人员在某些终结器上花了一点时间,并故意将其他花哨的3D演示放到了膝盖上。
最好避免,除非可能有test-env诊断程序。
埃克尔(Eckel)的《 Java思维》(The Thought in Java)对此做了很好的介绍。
我不确定我们能做什么,但是...
itsadok@laptop ~/jdk1.6.0_02/src/ $ find . -name "*.java" | xargs grep "void finalize()" | wc -l 41
因此,我猜太阳在某些情况下(他们认为)应该使用它。
finalize对捕获资源泄漏很有用。如果应关闭资源但不关闭资源,则将其未关闭的事实写到日志文件中,然后将其关闭。这样,我们就可以消除资源泄漏,并给自己提供一种方法来知道它已发生,因此可以对其进行修复。
从1.0 alpha 3(1995)开始,我就开始使用Java进行编程,但是我还没有重写finalize。
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!"); } }
===================================
结果:
这定案了
MyObject还活着!
=====================================
因此,我们可以在finalize方法中使无法访问的实例可访问。
编辑:好的,它确实不起作用。我实现了它,并认为有时如果失败了,那对我来说还可以,但是它甚至没有一次调用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); }