为什么finalize方法包含在Java中?


Answers:


41

根据约书亚·布洛赫(Joshua Bloch)的《有效Java(第二版)》,有两种情况下finalize()有用:

  1. 一种方法是充当“安全网”,以防对象所有者忘记调用其显式终止方法。虽然不能保证将立即调用终结器,但在客户端无法调用显式终止方法的那些(希望如此)的情况下(最好是极少数),释放资源比延迟释放更好。但是,如果终结器发现资源尚未终止,则终结器应记录一条警告

  2. 终结器的第二个合法用途是与本机对等对象有关。本机对等体是普通对象通过本机方法委托给的本机对象。由于本机对等体不是普通对象,因此垃圾回收器不了解它,并且在回收其Java对等体时也无法对其进行回收。终结器是执行此任务的合适工具,假定本地对等方不持有任何关键资源。如果本机对等方拥有必须立即终止的资源,则该类应具有显式终止方法,如上所述。终止方法应执行释放关键资源所需的任何操作。

要进一步阅读,请参阅第27页的项目7。


4
对于#2,听起来本地对等体是需要安全网的本地资源,应该具有终止方法,因此不应作为单独的点。
Mooing Duck

2
@MooingDuck:#2是一个单独的问题,因为如果本机对等点不拥有关键资源(例如,它纯粹是一个内存中对象),则无需公开显式终止方法。#1的安全网明确地涉及一种终止方法。
jhominal

55

终结器对于本地资源的管理很重要。例如,您的对象可能需要使用非Java API从操作系统分配WidgetHandle。如果在对对象进行GC处理时不释放该WidgetHandle,则将泄漏WidgetHandles。

重要的是,“永远不会调用finalizer”案例很简单:

  1. 该程序迅速关闭
  2. 该对象在程序的生存期内“永远存在”
  3. 计算机关闭/您的进程被操作系统等终止

在这三种情况下,您要么没有本机泄漏(由于程序不再运行),要么就已经有非本机泄漏(如果继续分配托管对象而不使用它们) GC')。

“不要依赖终结器被调用”警告实际上是关于不将终结器用于程序逻辑。例如,您不想跟踪在程序的所有实例中存在多少个对象,方法是在构造过程中在某个位置增加文件中的计数器,然后在终结器中减少它的数量,因为无法保证对象会最终,该文件计数器可能永远不会返回到0。这实际上是更一般原则的一种特殊情况,您不应依赖于程序正​​常终止(电源故障等)。

但是,对于本机资源的管理,终结器不运行的情况对应于您不关心终结器是否不运行的情况。


极好的答案。我当时想即使它可能被调用..它也应该包括在内。我对这个问题和StackOverflow上的链接链接感到困惑。
知悉2014年

3
并不是问题所在,但是在“不要依赖终结器被调用”的上下文中值得讨论:可以调用终结器,但是只能在对象变得不可访问之后任意漫长的延迟之后才进行。这对于比内存要稀缺得多的“本地资源”很重要(事实证明,这些资源大多数都是内存)。

26
“最终定稿对于本机资源的管理很重要。” -不,噢,抱歉。使用终结器来管理本机资源是一个可怕的想法(这就是为什么我们拥有autocloseable和co now的原因)。GC仅关心内存,而不关心其他资源。这意味着您可能会用完本机资源,但仍然有足够的内存来不触发GC-我们真的不想要那样。另外,终结器使GC更加昂贵,这也不是一个好主意。
Voo

12
我同意,除了终结器之外,您还应该有一个明确的本机资源管理策略,但是如果您的对象确实分配了它们并且没有在终结器中释放它们,那么在对象是GC没有显式释放资源。换句话说,终结器是本机资源管理策略的重要组成部分,而不是一般问题的解决方案。
Ryan Cavanaugh 2014年

1
@Voo说终结器是后备,如果未遵循对象的合同(close()在无法到达之前从未调用过),可能是更正确的选择
棘手异常2014年

5

API文档中解释了此方法的用途,如下所示:

当Java虚拟机确定不再有任何方法可以由尚未死亡的任何线程访问此对象时,将调用该方法,除非是由于某些其他对象的完成而采取的措施或即将结业的课程...

...的通常目的finalize在不可丢弃的对象被丢弃前执行清理动作。例如,代表输入/输出连接的对象的finalize方法可能会执行显式I / O事务,以在永久丢弃该对象之前断开连接...


如果您还对语言设计者为什么选择“对象不可撤消地丢弃”(垃圾回收)超出应用程序程序员无法控制的方式(“我们永远不要依赖”)的原因感兴趣,请参见对问题

自动垃圾收集...消除了困扰C和C ++程序员的整个编程错误类别。您可以放心地开发Java代码,以确保系统会迅速发现许多错误,并且直到生产代码交付后,重大问题才会处于休眠状态。

上面的引言来自于有关Java设计目标的官方文档,也就是说,可以将其视为解释Java语言设计者为何如此决定的权威参考。

有关此首选项的更详细且与语言无关的讨论,请参阅OOSC的9.6自动内存管理(实际上,如果您对这样的东西感兴趣,不仅此部分,而且整个第9章也非常值得阅读)。本节以明确的声明开头:

好的OO环境应该提供一种自动内存管理机制,该机制将检测并回收无法访问的对象,从而使应用程序开发人员可以专注于自己的工作-应用程序开发。

前面的讨论应该足以表明拥有这样一种设施的重要性。用Michael Schweitzer和Lambert Strether的话说:

没有自动内存管理的面向对象程序与没有安全阀的压力锅大致相同:迟早肯定会崩溃!


4

终结器之所以存在,是因为它们被认为是确保对事物进行清理的有效手段(即使在实践中不是),并且因为发明了终结者之后,可以确保清理的更好手段(例如幻像引用和try-with) -资源)尚不存在。事后看来,如果将用于实现其“最终化”功能的精力花在其他清理手段上,Java可能会更好,但是在最初开发Java的过程中这还不清楚。


3

卡瓦特:我可能已经过时了,但这是我几年前的理解:

通常,无法保证终结器何时运行-甚至根本无法运行,尽管某些JVM可以让您在程序退出之前请求完整的GC和终结处理(这当然意味着程序需要更长的时间)退出,这不是默认的操作模式)。

众所周知,某些GC会明确延迟或避免使用带有终结器的GC'ing对象,以期在基准测试上产生更好的性能。

不幸的是,这些行为与建议使用终结器的最初原因相抵触,并鼓励使用显式调用的关闭方法。

如果您有一个在丢弃之前确实必须清理的对象,并且如果您真的不信任用户这样做,那么终结器可能仍然值得考虑。但是总的来说,有充分的理由使您在现代Java代码中不像在某些早期示例中那样频繁地看到它们。


1

Google Java风格指南》对此主题提出了一些明智的建议:

覆盖是非常罕见的Object.finalize

提示:不要这样做。如果绝对必须,请非常仔细地阅读并理解有效的Java项目7,“避免使用终结器”,然后再不这样做。


5
我为什么钦佩这个问题过于简单,“不要这样做”,并不是对“为什么存在”的答案。
皮埃尔·阿洛德

1
我想我的答案没有很好地说明,但是(IMO)答案为“为什么存在?” 是“不应该”。对于那些确实需要完成操作的罕见情况,您可能希望自己使用PhantomReference和来构建它ReferenceQueue
Daniel Pryden 2014年

我的意思是*虽然我没有对你投反对票,但显然*。然而,最受支持的答案确实显示了该方法的实用性,这有点使您的观点无效。
皮埃尔·阿劳德

1
即使在本地同行的情况下,我认为PhantomReference也是更好的解决方案。终结器是Java早期遗留下来的疣,而喜欢Object.clone()和原始类型则是人们最容易忘记的语言的一部分。
Daniel Pryden 2014年

1
在C#中以更易于访问(开发人员可以理解)的方式描述了终结器的危险。,但是,一定不能暗示Java和C#的基础机制是相同的。在他们的规格书中没有这么说的。
rwong 2014年

1

Java语言规范(Java SE 7中)规定:

终结器提供了释放资源的机会,这些资源无法由自动存储管理器自动释放。在这种情况下,简单地回收对象使用的内存并不能保证将回收其持有的资源。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.