Java有析构函数吗?

664

Java有析构函数吗?我似乎找不到任何相关文档。如果没有,我该如何实现相同的效果?

为了让我的问题更具体,我正在编写一个处理数据的应用程序,并且规范要求有一个“重置”按钮,可以将应用程序恢复到刚启动时的状态。但是,所有数据都必须“实时”存在,除非应用程序关闭或按下重置按钮。

通常作为C/C++程序员,我认为这很容易实现。(因此我计划最后再实现它。)我将所有可“重置”的对象结构化到相同的类中,以便在按下重置按钮时可以销毁所有“实时”对象。

我在想,如果我所做的只是取消引用数据并等待垃圾收集器收集它们,那么如果我的用户反复输入数据并按下重置按钮,会不会出现内存泄漏?我也在思考,既然Java作为一种成熟的语言,应该有一种方法来避免这种情况发生或优雅地解决它。


9
只有在保留了不需要的对象引用时才会出现内存泄漏,也就是说,你的程序存在缺陷。垃圾回收器将根据需要运行(有时更早)。 - Peter Lawrey
22
如果你正在通过对象快速处理数据,虚拟机可能不会及时运行垃圾回收。认为垃圾回收器总能跟上进度或做出正确决策是一种谬论。 - Kieveli
2
@Kieveli 在出现错误之前,JVM不会运行GC吗? - WVrock
4
如果Java有一个能够将其一次性销毁的析构函数,那就太好了。 - Tomáš Zato
@WVrock - 有趣的问题。答案是“不行”(至少对于某些类型的“通过对象快速处理数据”),但原因很微妙。实际上,当您在垃圾回收中花费大约97%的时间,并且只有3%的时间用于实际程序逻辑时,您会遇到实际错误,因为大多数引用仍然具有指向它们的指针。如果“快速处理”使用少量指针,则不会出现问题。 - sf_jeff
显示剩余2条评论
24个回答

4

如果你只是担心内存问题,那就不用担心。相信垃圾回收器,它做得很好。实际上,我看到过一些关于它非常高效的东西,有时创建大量小对象可能比利用大数组更好地提高性能。


3
也许你可以使用try...finally块来在使用对象的控制流中完成对象的终止。当然,这并不是自动发生的,但在C++中销毁也不是自动的。通常在finally块中看到资源的关闭。

1
当所涉及的资源具有单一所有者并且从未被其他代码“窃取”引用时,这就是正确的答案。 - Steve Jessop

3

Lombok中有一个@Cleanup注释,它大多类似于C++的析构函数:

@Cleanup
ResourceClass resource = new ResourceClass();

在处理它时(编译时),Lombok会插入适当的try-finally块,以便在执行离开变量范围时调用resource.close()。您还可以明确指定释放资源的另一种方法,例如resource.dispose()

@Cleanup("dispose")
ResourceClass resource = new ResourceClass();

2
我看到的优点是会有更少的嵌套(如果你有很多需要“销毁”的对象,这可能非常重要)。 - Alexey
一个 try-with-resource 块可以一次性拥有多个资源。 - Alexander
1
但是它们之间不能有指令。 - Alexey
公平的。那么我认为建议是优先使用try-with-resource,即使有多个资源,除非它们之间需要强制您进行新的try-with-resource块(增加嵌套),然后使用@Cleanup - Alexander

2
如果您有使用诸如Weld之类的上下文和依赖注入(CDI)框架的机会,您可以使用Java注释@Predestroy来执行清理工作等。
@javax.enterprise.context.ApplicationScoped
public class Foo {

  @javax.annotation.PreDestroy
  public void cleanup() {
    // do your cleanup    
  }
}

即使不是这个,一个全局对象管理器也可以解决 OP 最初的问题(即“重置按钮”)。 - ryanwebjackson

2
在这里有很多好的回答,但是关于为什么应该避免使用finalize()还有一些额外的信息。
如果JVM由于System.exit()Runtime.getRuntime().exit()而退出,则默认情况下不会运行finalizer。从Runtime.exit()的Javadoc中可以看到:

虚拟机的关闭顺序分为两个阶段。在第一个阶段中,如果有任何已注册的关闭挂钩,则以某种未指定的顺序启动它们,并允许它们并发运行,直到它们完成。在第二个阶段中,如果启用了退出时终结处理,则运行所有未调用的终结处理程序。完成此操作后,虚拟机停止。

您可以调用System.runFinalization(),但它只是“尽最大努力完成所有未完成的终结处理” - 而不是保证。
这里有一个System.runFinalizersOnExit()方法,但不要使用它——它是不安全的,并且早已被弃用。

2
在Java中,最接近析构函数的方法是finalize()方法。与传统的析构函数相比,最大的区别是你无法确定它何时被调用,因为这是垃圾回收器的责任。在使用它之前,我强烈建议仔细阅读相关资料,因为使用finalize()方法时,文件句柄等典型的RAIA模式不会可靠地工作。

2
只是思考原问题...我认为我们可以从所有其他学过的答案和Bloch的基本《Effective Java》(第二版)中得出结论,以及项目7“避免使用finalize” ,它以Java语言不适当的方式寻求合法问题的解决方案。
...那么,如果您需要重置的所有对象都保存在一种“游乐场”中,那么实际上OP想要做的相当明显的解决方案是通过某种访问器对象引用所有其他不可重置的对象。然后当您需要“重置”时,断开现有游乐场并创建一个新的游乐场:游乐场中的所有对象网络都被抛弃,永远不会返回,并且有朝一日会被GC收集。
如果这些对象中有任何一个是Closeable(或者没有但是有一个close方法),您可以在创建它们(可能打开它们)时将它们放入playpen中的Bag中,并在切断playpen之前,accessor的最后一个操作是遍历所有的Closeables并关闭它们。
代码可能看起来像这样:
accessor.getPlaypen().closeCloseables();
accessor.setPlaypen( new Playpen() );

closeCloseables 可能是一个阻塞方法,可能涉及到一个门闩(例如 CountdownLatch),以处理(并在适当时等待)任何特定于 Playpen 的线程中的 Runnables/Callables,以便适当地结束,特别是在 JavaFX 线程中。


0
如果您正在编写Java Applet,可以重写Applet的“destroy()”方法。它是...
 * Called by the browser or applet viewer to inform
 * this applet that it is being reclaimed and that it should destroy
 * any resources that it has allocated. The stop() method
 * will always be called before destroy().

显然不是你想要的,但可能是其他人正在寻找的。


0

从我刚才扫描的所有答案中缺少的是更安全的替代终结器。所有其他答案都正确,关于使用try-with-resources和避免终结器,因为它们不可靠且现在已被弃用...

然而,他们没有提到Cleaners。Cleaners是在Java 9中添加的,以比终结器更好地明确处理清理工作。

https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html


0

虽然Java的GC技术已经有了相当大的进步,但你仍然需要谨慎处理你的引用。我想起了许多看似微不足道的引用模式,实际上却是混乱不堪。

从你的帖子来看,似乎你并不打算实现重置方法以便对象的重复使用(对吗?)。你的对象是否持有其他类型的资源,需要清理(例如必须关闭的流、必须返回的任何池化或借用的对象)?如果你所担心的只是内存释放,那么我建议重新考虑我的对象结构,并尝试验证我的对象是否是自包含结构,在GC时间将被清理。


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