java 的优雅停机有两种实现方式:
1、采用默认信号处理机制,通过 Runtime.getRuntime().addShutdownHook(new ShudownHookThread());
实现收尾进程的注册,这样在收到默认正常结束信号 ( SIGINT(2) 和 SIGTERM(15) ) 就可优雅退出
2、采用自定义信号处理机制,通过 Signal.handle(new Signal("USR2"), new SignalHandlerImp());
注册 自定义信号 以及 信号处理实现类,这样使用 kill -自定义信号 ( 如: SIGUSR2(12) ) [PID] 就可以达到收尾操作在 信号处理实现类 里实现,从而也可实现优雅退出
注意事项:
- ShutdownHook 在某些情况下并不会被执行,例如JVM崩溃,无法接受信号量和kill -9 pid等
- 当存在多个 ShutdownHook 时,JVM 无法保证它们的执行先后顺序
- 在JVM关闭期间不能动态添加或者取出ShutdownHook
- 不能在 ShutdownHook中调用System.exit(),会卡住JVM,导致进程无法退出
- 对于采用注册 SignalHandler 实现优雅退出的程序,在 handler 接口中一定要避免阻塞操作,否则会导致已经注册的 ShutdownHook 无法执行,系统也无法退出
注册 jdk 的 ShutdownHook
当系统接受退出指令时,首先标记系统处于退出状态,不再接受新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,各线程退出执行
开发步骤:
1、定义一个收尾线程,用于完成相关的任务,如资源释放,删除缓存文件等
2、在 Main 函数中,主程序启动之前注册收尾线程
3、注意:该收尾线程收到 Linux 的 SIGINT(2) 或 SIGTERM(15) 信号量时才会被调用,其他的信号量该收尾线程不会被调用
public class Main {
public static void main(String[] args) throws InterruptedException {
// 获取运行时
Runtime runtime = Runtime.getRuntime();
// 注册一个 ShutdownHook
runtime.addShutdownHook(new Thread(()->{
System.out.println("开始执行优雅退出");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("优雅退出完成");
}));
System.out.println("程序执行中");
TimeUnit.SECONDS.sleep(3);
System.out.println("程序执行完毕,开始退出");
System.exit(0);
}
}
监听信号量并注册 SignalHandler
sun.misc.SignalHander 的工作原理:
Linux 中的信号量:
- kill -l:查看所有支持的信号序号和信号名
- kill -s 9 [pid]:向 PID 所在的进程发送信号 9,支持信号序号和信号名,因此该命令可以写成 kill -s SIGKILL [pid],也可以简写成 kill -9 [pid]
- 常见的信号量:
- SIGINT 2:中断,等同于 Ctrl + C,用于前台进程快捷终止
- SIGTERM 15:正常终止,用于后台进程
- SIGKILL 9:强制终止,用于后台进程,不支持注册
注册 Signal:Signal.handle(Signal signal, SignalHandler handler)
- 通过实现 SignalHandler 来自定义信号量处理逻辑
- signal:绑定的要处理的信号量
简单实现:
public class SignalDemo {
private static String getOSSignalType() {
return System.getProperties().getProperty("os.name")
.toLowerCase().startsWith("win") ? "INT" : "TERM";
}
public static void main(String[] args) throws InterruptedException {
// 如果是Windows监听Ctrl+C信号量,如果是Linux监听TERM信号量
String signalType = getOSSignalType();
System.out.println(signalType);
// 注册
Signal.handle(new Signal(signalType), signal -> {
// 打印接受到的信号
System.out.println(signal.getName() + ":" + signal.getNumber());
// 退出JVM
System.exit(0);
});
int count = 0;
while (true) {
TimeUnit.SECONDS.sleep(3);
System.out.println(count++);
}
}
}
// 在控制台运行,使用ctrl+c关闭