为JVM应用程序添加关闭钩子
1. 概述
启动服务通常很容易。但是,有时我们需要制定一个优雅地关闭一个的计划。
在本教程中,我们将了解 JVM 应用程序可以终止的不同方式。然后,我们将使用 Java API 来管理 JVM 关闭钩子。请 参阅本文 以了解有关在 Java 应用程序中关闭 JVM 的更多信息。
2. JVM关闭
JVM 可以通过两种不同的方式关闭:
- 受控过程
- 突然的方式
在以下任一情况下,受控进程会关闭 JVM:
- 最后一个非守护 线程终止。例如,当主线程退出时,JVM 开始其关闭进程
- 从操作系统发送中断信号。例如,通过按
Ctrl + C
或注销操作系统 - 从 Java 代码调用 System.exit()
虽然我们都在努力实现优雅的关闭,但有时 JVM 可能会以突然和意外的方式关闭。JVM 在以下情况下突然关闭:
- 从操作系统发送终止信号。例如,通过发出 kill -9 jvm_pid
- 从 Java 代码调用 Runtime.getRuntime().halt()
- 主机操作系统意外死机,例如电源故障或操作系统崩溃
3. 关闭钩子
JVM 允许注册函数在其完成关闭之前运行。这些功能通常是释放资源或其他类似内务任务的好地方。在 JVM 术语中,这些函数称为 shutdown hooks。
关闭钩子基本上是已初始化但未启动的线程。当 JVM 开始其关闭过程时,它将以未指定的顺序启动所有已注册的钩子。运行所有钩子后,JVM 将停止。
3.1. 添加钩子
为了添加关闭钩子,我们可以使用 *Runtime.getRuntime().addShutdownHook() *方法:
Thread printingHook = new Thread(() -> System.out.println("In the middle of a shutdown"));
Runtime.getRuntime().addShutdownHook(printingHook);
在这里,我们只是在 JVM 自行关闭之前将一些内容打印到标准输出。如果我们像下面这样关闭 JVM:
> System.exit(129);
In the middle of a shutdown
然后我们将看到钩子实际上将消息打印到标准输出。
JVM 负责启动钩子线程。因此,如果给定的钩子已经启动,Java 将抛出异常:
Thread longRunningHook = new Thread(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException ignored) {}
});
longRunningHook.start();
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(longRunningHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Hook already running");
显然,我们也不能多次注册一个钩子:
Thread unfortunateHook = new Thread(() -> {});
Runtime.getRuntime().addShutdownHook(unfortunateHook);
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(unfortunateHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Hook previously registered");
3.2. 拆除钩子
Java 提供了一个双重removeShutdownHook方法来在注册后删除特定的关闭钩子:
Thread willNotRun = new Thread(() -> System.out.println("Won't run!"));
Runtime.getRuntime().addShutdownHook(willNotRun);
assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();
当成功删除关闭钩子时,removeShutdownHook() 方法返回 true 。
3.3. 注意事项
**JVM 仅在正常终止的情况下运行关闭钩子。**因此,当外力突然杀死 JVM 进程时,JVM 将没有机会执行关闭钩子。此外,从 Java 代码中停止 JVM 也会产生相同的效果:
Thread haltedHook = new Thread(() -> System.out.println("Halted abruptly"));
Runtime.getRuntime().addShutdownHook(haltedHook);
Runtime.getRuntime().halt(129);
halt 方法强制终止当前运行的 JVM 。因此,注册的关闭钩子将没有机会执行。