Contents

为JVM应用程序添加关闭钩子

1. 概述

启动服务通常很容易。但是,有时我们需要制定一个优雅地关闭一个的计划。

在本教程中,我们将了解 JVM 应用程序可以终止的不同方式。然后,我们将使用 Java API 来管理 JVM 关闭钩子。请 参阅本文 以了解有关在 Java 应用程序中关闭 JVM 的更多信息。

2. JVM关闭

JVM 可以通过两种不同的方式关闭:

  1. 受控过程
  2. 突然的方式

在以下任一情况下,受控进程会关闭 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 。因此,注册的关闭钩子将没有机会执行。