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) 信号量时才会被调用,其他的信号量该收尾线程不会被调用

  1. public class Main {
  2. public static void main(String[] args) throws InterruptedException {
  3. // 获取运行时
  4. Runtime runtime = Runtime.getRuntime();
  5. // 注册一个 ShutdownHook
  6. runtime.addShutdownHook(new Thread(()->{
  7. System.out.println("开始执行优雅退出");
  8. try {
  9. TimeUnit.SECONDS.sleep(3);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println("优雅退出完成");
  14. }));
  15. System.out.println("程序执行中");
  16. TimeUnit.SECONDS.sleep(3);
  17. System.out.println("程序执行完毕,开始退出");
  18. System.exit(0);
  19. }
  20. }

监听信号量并注册 SignalHandler

sun.misc.SignalHander 的工作原理:
未命名绘图.png

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:绑定的要处理的信号量

简单实现:

  1. public class SignalDemo {
  2. private static String getOSSignalType() {
  3. return System.getProperties().getProperty("os.name")
  4. .toLowerCase().startsWith("win") ? "INT" : "TERM";
  5. }
  6. public static void main(String[] args) throws InterruptedException {
  7. // 如果是Windows监听Ctrl+C信号量,如果是Linux监听TERM信号量
  8. String signalType = getOSSignalType();
  9. System.out.println(signalType);
  10. // 注册
  11. Signal.handle(new Signal(signalType), signal -> {
  12. // 打印接受到的信号
  13. System.out.println(signal.getName() + ":" + signal.getNumber());
  14. // 退出JVM
  15. System.exit(0);
  16. });
  17. int count = 0;
  18. while (true) {
  19. TimeUnit.SECONDS.sleep(3);
  20. System.out.println(count++);
  21. }
  22. }
  23. }
  24. // 在控制台运行,使用ctrl+c关闭