Java 中的 JVM 关机钩

10 Sept 2024 | 4 分钟阅读

开发人员可以使用一种称为关闭钩子的特定构造,在 JVM 关闭时插入一段代码来运行。当我们需要在 JVM 关闭时执行某些清理程序时,它很有用。

当虚拟机因用户外部原因(例如操作系统发出的终止请求)或由于资源问题(例如内存不足)而关闭时,使用常规构造(例如在应用程序退出前调用一个特殊过程(调用 System.exit(0)))将不起作用。关闭钩子允许我们提供一个任意代码块,该代码块将在 JVM 关闭时被调用,从而轻松解决此问题,正如我们稍后将看到的。

乍一看,使用关闭钩子似乎非常简单。要执行我们想要在虚拟机关闭时执行的逻辑,我们需要创建一个扩展 `java.lang.Thread` 类的类。

使用 Runtime.getRuntime() 方法

使用 `Runtime.getRuntime()` 方法,可以在 Java 中创建关闭钩子。请观察以下程序。

文件名: ShutDownHook.java

输出

Application Terminating ...
Shutdown Hook is running !                                                                                                        

通过扩展 Thread 类

在这种情况下,我们必须将子线程添加到关闭钩子中。

文件名: ShutDownHook1.java

输出

3 X 1 = 3
3 X 2 = 6
3 X 3 = 9
3 X 4 = 12
3 X 5 = 15
3 X 6 = 18
3 X 7 = 21
3 X 8 = 24
3 X 9 = 27
3 X 10 = 30
In the clean up code
In the shutdown hook

关于关闭钩子的要点

在处理关闭钩子时,应牢记以下几点。

1. 在某些情况下,关闭钩子可能不会执行!

首先要记住的是,关闭钩子可能不总是执行。如果 JVM 由于内部故障而崩溃,它可能会在开始处理任何指令之前就发生。此外,如果操作系统发送 SIGKILL(在 Unix/Linux 中为 kill -9)或 TerminateProcess(Windows)信号,程序必须立即结束,甚至不需要等待任何清理程序。除了上述选项之外,还可以使用 `Runtime.halt()` 方法来终止 JVM,而无需启用关闭钩子。

当应用程序正常退出时(当所有线程都运行完毕或调用 `System.exit(0)` 时),会激活关闭钩子。此外,当 JVM 由于外部因素终止时,例如用户请求终止(Ctrl+C)、操作系统的 SIGTERM 信号(标准 kill 命令,不带 -9),或操作系统终止时。

2. 关闭钩子可以在开始后被强制终止,而无需完成。

实际上,这是前一种情况的一个特例。尽管钩子已经开始运行,但在操作系统关闭等情况下,它仍有可能过早结束。在这种情况下,在收到 SIGTERM 信号后,操作系统会等待一段时间以供进程结束。如果进程在此时间内未结束,操作系统将发出 SIGTERM(或 Windows 等效信号)以强制结束进程。因此,这可能会发生在关闭钩子执行过程中。因此,建议仔细构建关闭钩子,确保它们快速完成并且不会导致死锁等情况。此外,JavaDoc [1] 明确指出,不应该在关闭钩子中等待用户 I/O 操作或执行长时间计算。

3. 尽管许多关闭钩子的执行顺序无法保证。

您可以注册多个关闭钩子,正如您从 `addShutdownHook` 方法的名称(而不是 `setShutdownHook`)中可能已经正确推断出的那样。然而,JVM 不保证这些多个钩子将被执行的顺序。JVM 可能会以任何随机顺序执行关闭钩子。此外,JVM 可能会同时运行所有这些钩子。

4. 不允许在关闭钩子中注册或注销关闭钩子

一旦 JVM 开始关闭过程,就不可能添加新的关闭钩子或删除已有的关闭钩子。如果尝试这样做,JVM 将发出 `IllegalStateException`。

5. `Runtime.halt()` 是唯一可以在关闭序列开始后终止它的方法。

除了 SIGKILL 等外部影响外,一旦关闭序列开始执行,只有 `Runtime.halt()`(它会强制终止 JVM)才能中断其执行。因此,无法在关闭钩子中使用 `System.exit()`。实际上,如果您在关闭钩子中使用 `System.exit()`,VM 可能会卡住,在这种情况下,我们可能需要强制终止进程。

6. 使用关闭钩子需要安全权限。

如果正在使用 Java 安全管理器,则添加或删除关闭钩子的代码必须具有运行时访问关闭钩子权限的权限。如果在安全环境中未经授权调用此方法,将抛出 `SecurityException`。