关机钩子 vs 终结器方法

13

我不明白为什么必须使用Runtime.addShutdownHook。如果你想在jvm退出时进行一些清理工作,为什么不只是过载守护类的finalize方法呢?使用shutdown hook相比finalize方法有何优势?

此外还有一个已弃用的函数runFinalizersOnExit。如果将其设置为false,我相信finalizers将不会运行。这与Java保证finalizers始终在垃圾回收之前运行的规定相矛盾。


4
这与Java的保证相矛盾,即finalizer总是在垃圾收集之前运行。… Java有这样的保证吗?实际上并没有。当创建这种方法时,多核处理器还不普及,且并发性较少,但现在...它非常不安全,可能会导致不可逆的系统损坏。 没有人应该使用runFinalizersOnExit - scottb
2个回答

23

不能保证最终器(finalizer)一定会运行。当对象被垃圾收集器(garbage collector)回收时,会调用finalize()方法。但是,程序运行时垃圾收集器可能不会回收任何东西。

相比之下,JVM正常退出时会运行关闭挂钩(shutdown hooks)。所以即使如此也不能百分之百地保证,但它非常接近。只有极少数情况下关闭挂钩不会运行。

编辑 我查看了关闭挂钩未执行的极端情况:

关闭挂钩被执行:

  • 当所有JVM线程都已完成执行时
  • 因为调用了System.exit()
  • 因为用户按下CNTRL-C
  • 系统级关机或用户注销

关闭挂钩不会被执行:

  • 如果VM由于本机代码错误而崩溃,则无法保证是否会运行挂钩。
  • 如果使用Linux上的-kill命令或在Windows上终止进程杀掉JVM,则JVM会立即退出

如果我没记错的话,只有守护线程才绝对不会运行 finalize 函数。您能否解释一下为什么普通线程可能不会运行 finalize 函数?此外,在优雅关闭期间关闭挂钩不会运行的边缘情况是什么? - S Kr
@SKr 如果虚拟机立即退出,那么实际线程结束后,Thread对象可能无法被垃圾回收。 - Darkhogg
@Darkhogg 如果你指的是JVM突然关闭,那么即使是Shutdown Hook也不会执行。 - S Kr
3
不,我是在谈论正常的关闭。最后一个线程结束 -> 虚拟机退出。没有垃圾回收,也没有终结器。 - Darkhogg
@SKr 更新了答案,并说明了何时运行关闭挂钩和不运行。 - dkatzel

5

关于您的问题

如果您想在jvm退出时进行一些清理,为什么不重载守护进程类的finalize方法呢?

我从这篇文章中找到了很好的信息。

  1. finalize()在Garbage Collector回收对象之前被调用。JVM不能保证何时调用此方法。

  2. finalize()只会被GC线程调用一次,如果对象从finalize方法中恢复,则不会再次调用finalize方法。

  3. 在您的应用程序中,可能有一些活动对象,垃圾收集器永远不会调用它们。

  4. GC线程忽略由finalize方法抛出的任何异常。

  5. System.runFinalization(true)Runtime.getRuntime().runFinalization(true)方法增加了调用finalize()方法的概率,但现在这两个方法已经过时了。这些方法非常危险,因为缺乏线程安全性和可能导致死锁。

回到shutdownHooks,根据Oracle文档

public void addShutdownHook(Thread hook) 注册一个新的虚拟机关闭挂钩。

Java虚拟机响应两种事件而关闭:

  1. 程序正常退出,即最后一个非守护线程退出或调用exit(等效于System.exit)方法时,或者
  2. 在用户中断(例如键入^C)或系统范围事件(例如用户注销或系统关闭)的响应下终止虚拟机。
  3. 当虚拟机开始其关闭序列时,它将以某些未指定的顺序启动所有已注册的关闭挂钩,并让它们并发运行。当所有挂钩都完成后,如果已启用了退出时进行终结,则会运行所有未调用的终结器。
  4. 最后,虚拟机将停止。请注意,在关闭序列期间,守护线程将继续运行,如果通过调用exit方法启动关闭,则非守护线程也将继续运行。

但即使Oracle文档也引用:

关闭挂钩也应尽快完成其工作。当程序调用exit时,预期是虚拟机会立即关闭并退出。

在罕见情况下,虚拟机可能会中止,即在不干净地关闭的情况下停止运行。

考虑到这两种方法的缺点,您应该遵循以下方法:

  1. 不要依赖finalize()shutdown hooks释放应用程序中的关键资源。

  2. 适当使用try{} catch{} finally{}块,并在finally{}块中释放关键资源。在释放finally{}块中的资源时,捕获ExceptionThrowable


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