为什么你会执行finalize()?
我已经阅读了很多关于finalize()的新手java问题,并且发现有一种令人困惑的理解,即没有人真正明白finalize()是清理资源的不可靠方法。 我看到有人评论说他们使用它来清理连接,这真的很可怕,因为唯一能够保证Connectionclosures的唯一方法是最后实现try(catch)。
我没有在CS学习,但是我已经在Java专业编程了近十年了,我从来没有见过任何人在生产系统中实现finalize()。 这并不意味着它没有用处,或者我曾经工作过的人一直在做正确的事情。
所以我的问题是,有什么用例来实现finalize(),不能通过语言中的另一个进程或语法更可靠地处理?
请提供具体的场景或者你的经验,简单的重复一下Java的教科书,或者敲定用途是不够的,而不是这个问题的意图。
您可以将其用作持有外部资源(套接字,文件等)的对象的逆止器。 实现close()
方法并logging它需要被调用的地方。
执行finalize()
来执行close()
处理,如果你发现它还没有完成的话。 也许有些东西被转移到stderr指出,你是一个错误的来电后清理。
它提供了额外的安全性在一个特殊的/越野车的情况。 不是每个来电者都会每次都做正确的try {} finally {}
。 不幸的是,在大多数环境下都是如此。
我同意这很less需要。 正如评论者指出的那样,它带有GC开销。 只有在长时间运行的应用程序中需要“腰带和吊带”安全的情况下才能使用。
从Java 9开始, Object.finalize()
已经被弃用了! 他们把我们指向java.lang.ref.Cleaner
和java.lang.ref.PhantomReference
作为select。
finalize()是JVM的暗示,它可能是很好的执行你的代码在未指定的时间。 当你想让代码神秘地失败时,这是很好的。
在终结者(除了日志logging之外基本上任何事情)做的任何事情都有三种情况:
- 你想赌博其他最终的对象将仍然处于其他程序认为有效的状态。
- 你想添加大量的检查代码到所有你的类的所有方法都有一个终结器,以确保他们的行为正确后,最终确定。
- 你想不小心复活最终的对象,并花费大量的时间来弄清楚为什么他们不工作,和/或为什么他们最终被释放时没有完成。
如果你认为你需要finalize(),有时候你真正想要的是一个幻影引用(在给出的例子中,它可以持有一个由它的referand使用的连接的硬引用,并在幻像引用排队后closures它)。 这也有它可能神秘地永远不会运行的属性,但至less它不能调用或复活最终对象的方法。 所以,对于那些你并不一定需要干净地closures这个连接的情况来说,这是恰到好处的,但是你很喜欢,而且你的class级的客户不能或者不会自己closures(这实际上是足够公平的 – 如果你devise的接口在收集之前需要一个特定的动作,那么根本就有一个垃圾收集器的意义是什么?这只是让我们回到了malloc / free的时代。)
其他时候,你需要你认为自己的资源更健壮的资源。 例如,为什么你需要closures连接? 它最终必须基于系统提供的某种I / O(套接字,文件,任何),那么为什么当最低级别的资源被占用时,为什么不能依靠系统来closures它? 如果另一端的服务器完全要求你closures连接,而不是简单地closures连接,那么当有人绊倒你的代码运行的机器的电源线,或者中间networking出去时,会发生什么?
免责声明:我曾经在JVM实现上工作过。 我讨厌终结者。
一个简单的规则:不要使用终结器。 事实上,一个对象有一个终结器(不pipe它执行什么代码)足以导致垃圾收集相当大的开销。
从Brian Goetz的一篇文章 :
带终结器的对象(那些有非平凡的finalize()方法的对象)与没有终结器的对象相比,有相当大的开销,应该谨慎使用。 Finalizeable对象分配速度较慢,收集速度较慢。 在分配时,JVM必须向垃圾收集器注册任何可终结对象,并且(至less在HotSpot JVM实现中)可终结对象必须遵循比大多数其他对象更慢的分配path。 同样,可最终确定的对象收集速度也较慢。 在可回收可最终对象之前,至less需要两个垃圾收集周期(在最好的情况下),并且垃圾收集器必须做额外的工作来调用终结器。 结果是更多的时间花在分配和收集对象上,对垃圾收集器施加更多的压力,因为无法访问的可finalize对象所使用的内存被保留的时间更长。 结合这个事实,即终结器不能保证在任何可预测的时间范围内运行,甚至根本不能保证运行,并且你可以看到,终结是正确的工具使用的情况相对较less。
唯一一次我在生产代码中使用finalize来实现一个检查,一个给定的对象的资源已被清理,如果没有,然后logging一个非常有声音的消息。 它实际上并没有尝试自己做,如果做得不好,它只会大声嚷嚷。 原来是相当有用的。
自1998年以来,我一直在专业从事Java,而我从来没有实现过finalize()。 不止一次。
我不知道你能做什么,但…
itsadok@laptop ~/jdk1.6.0_02/src/ $ find . -name "*.java" | xargs grep "void finalize()" | wc -l 41
所以我猜Sun会发现一些他们认为应该使用的情况。
我用一次finalize来理解什么对象被释放。 你可以用静力学,引用计数等等来玩一些精巧的游戏,但这只是为了分析。
接受的答案是好的,我只是想补充一点,现在有一种方法来实现finalize的function,而不用实际上使用它。
看看“参考”类。 弱参考等
您可以使用它们来保留对所有对象的引用,但是此引用ALONE不会停止GC。 关于这个的整洁的事情是你可以让它调用一个方法,当它将被删除,并且这个方法可以保证被调用。
另一件要注意的事情。 任何时候你都可以在代码中看到任何类似的东西(不仅仅是最终确定的,而是你最有可能看到的东西):
public void finalize() { ref1 = null; ref2 = null; othercrap = null; }
这是一个迹象,有人不知道他们在做什么。 像这样清理几乎是不需要的。 当class级GC'd,这是自动完成的。
如果你在最终确定的代码中find这样的代码,那么保证写这个代码的人感到困惑。
如果它在其他地方,可能是代码是一个坏模型的有效补丁(一个类留在很长一段时间,由于某些原因引用的东西必须在对象GC'd之前手动释放)。 一般是因为有人忘记删除一个听众或某个东西,不知道为什么他们的对象不被GC'd所以他们只是删除它所指的东西,耸耸肩,走开。
它不应该被用来清理“更快”的东西。
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方法中创build一个无法访问的实例。
finalize可以用来捕获资源泄漏。 如果资源应该closures,但不写入closures日志文件的事实并closures它。 通过这种方式,您可以删除资源泄漏,并让自己知道发生了什么,以便您可以修复它。
自从1.0 alpha 3(1995)以来,我一直使用Java进行编程,而且我还没有重写最终的任何内容。
你不应该依赖finalize()来清理你的资源。 然后finalize()不会运行,直到类被垃圾回收。 在使用完资源后明确释放资源要好得多。
为了突出上述答案中的一点:终结器将在独立的GC线程上执行。 我曾经听说过一个主要的Sun演示,开发人员给一些终结者增加了一个小小的睡眠,并有意地把一个本来很花哨的3D演示带到了膝盖上。
最好避免,可能的例外是testing-env诊断。
Eckel在Java中的思考在这方面有一个很好的部分 。
在编写代码时,其他开发人员需要使用某种“清除”方法来释放资源。 有时候那些其他的开发者会忘记调用你的清理(或者closures,或者销毁等)方法。 为了避免可能的资源泄漏,你可以检查finalize方法来确保方法被调用,如果不是,你可以自己调用它。
许多数据库驱动程序在它们的Statement和Connection实现中都是这样做的,以便对忘记调用close的开发人员提供一些安全性。
嗯,我曾经用它来清理没有被返回到现有池中的对象。 他们被传了很多,所以不可能告诉他们什么时候可以安全地返回到游泳池。 问题是它在垃圾收集过程中引入了巨大的惩罚,远远大于集中对象的任何节余。 整整一个月的时间里,我已经在生产了大约一个月的时间,把所有的东西都变成dynamic的,并且完成了。
编辑:好的,它真的不起作用。 我实现了它,并认为如果失败有时对我来说没问题,但它甚至没有一次调用finalize方法。
我不是一个专业的程序员,但在我的程序中,我有一个案例,我认为是使用finalize()的一个好例子,这是一个caching,在其销毁之前将其内容写入磁盘。 因为没有必要每一次都被破坏,所以只会加快我的程序,希望我没有做错。
@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); } @覆盖 protected void finalize()throws Throwable { super.finalize(); if(weakAwtEventListener!= null){ 。Toolkit.getDefaultToolkit()removeAWTEventListener(weakAwtEventListener); } }
在finalize()中要小心你的做法。 特别是如果你使用它来调用close()来确保资源被清理。 我们遇到了几种情况,我们把JNI库链接到正在运行的java代码中,在任何情况下,我们使用finalize()来调用JNI方法,我们会得到非常糟糕的java堆损坏。 腐败不是由底层的JNI代码本身造成的,所有的内存跟踪在本地库中都没问题。 只是我们从finalize()中调用了JNI方法。
这是一个仍然在广泛使用的JDK 1.5。
直到后来我们才发现出了什么问题,但最终罪魁祸首就是使用JNI调用的finalize()方法。
接受的答案列出了在finalize期间closures一个资源可以完成。
然而, 这个答案表明,至less在JIT编译器的java8中,会遇到意外的问题,即使在从对象维护的stream读完之前,有时甚至会调用终结器。
所以即使在这种情况下,我们也不build议调用finalize。
就个人而言,我几乎从来没有使用finalize()
除了在一个罕见的情况:我做了一个自定义的genericstypes的集合,我写了一个自定义的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
是我做的一个接口,可以让你指定实现了很less实现的Object
方法,如#finalize()
, #hashCode()
和#clone()
)
所以,使用姐妹#setDestructivelyFinalizes(boolean)
方法,使用我的集合的程序可以(帮助)保证销毁对这个集合的引用也会破坏对它的内容的引用,并且放置任何可能使JVM无意中活着的窗口。 我考虑也停止任何线程,但是这打开了一个全新的蠕虫jar。
一旦完成使用,资源(文件,套接字,stream等)需要closures。 它们通常有close()
方法,我们通常在try-catch
语句的finally
一节中调用它。 有时finalize()
也可以被less数开发者使用,但是IMO不是一个合适的方式,因为不能保证finalize会被永远调用。
在Java 7中,我们得到了try-with-resources声明,可以像这样使用:
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通过调用close()
方法自动closures资源BufferedReader
。 如果我们想要,我们也可以在我们自己的类中实现Closeable ,并以类似的方式使用它。 国际海事组织似乎更清楚和简单的理解。
iirc – 你可以使用finalize方法作为实现资源池机制的手段 – 所以他们也不会得到GC。