启动一个服务相对容易,但有时我们需要能控制并优雅地关闭服务。本文介绍几种不同方式终止JVM应用,然后使用JAVA API管理JVM关闭回调。
关闭JVM
有两种放啊是关闭JVM:
- 控制进程
- 意外方式
下面几种情况也属于控制进程关闭JVM:
- 最后的非精灵现场终止,例如当主线程结束时,JVM开始关闭进程。
- 从操作系统发送中断信号,例如,按CTRL+C或注销操作系统
- 通过Java代码调用
System.exit()
当我们希望JVM优雅地关闭,但有时JVM可能被异常方式关闭:
- 从OS发送kill信号,例如:
kill -9 <jvm_pid>
- 从JAVA 代码调用
Runtime.getRuntime().halt()
- OS异常死机,例如,断电或OS死机
关闭JVM的回调钩子
JVM支持在完成关闭JVM之前注册函数,这些函数是释放资源或其他任务管理的好地方。在JVM术语中,这些函数被称为关闭钩子。
关闭钩子其实是初始化但未启动的线程。当JVM开始其关闭进程时,它将以未指定的顺序启动所有注册的钩子。在运行所有钩子函数之后,JVM将停止。
注册钩子函数
注册钩子函数,可以使用 Runtime.getRuntime().addShutdownHook()
方法:
‘’’
Thread printingHook = new Thread(() -> System.out.println(“In the middle of a shutdown”));
Runtime.getRuntime().addShutdownHook(printingHook);
‘’’
这里演示在JVM关闭之前在控制台打印一些信息:
> System.exit(129);
// In the middle of a shutdown
我们看到钩子函数确实如期望在控制台打印了信息。
JVM负责启动钩子函数,如果钩子函数已经启动,JVM会抛出异常:
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");
删除钩子函数
Java 提供对应的删除函数用于删除已注册的关闭钩子函数:
Thread willNotRun = new Thread(() -> System.out.println("Won't run!"));
Runtime.getRuntime().addShutdownHook(willNotRun);
assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();
当成功删除钩子函数后,removeShutdownHook()
函数返回true。
说明
JVM仅在正常关闭时才会运行钩子函数。因此当外部暴力kill jvm进程,并不会获得机会执行钩子函数。另外,从java代码暂停JVM也不会执行钩子函数:
Thread haltedHook = new Thread(() -> System.out.println("Halted abruptly"));
Runtime.getRuntime().addShutdownHook(haltedHook);
Runtime.getRuntime().halt(129);
`halt()'方法强制终止当前运行的JVM,因此注册的钩子函数不会有机会执行。
总结
本文介绍关闭JVM的几种方式,然后使用运行时API注册或删除钩子函数。