写点什么

Java 应用的优雅停机总结

作者:陈德伟
  • 2022 年 7 月 08 日
  • 本文字数:2100 字

    阅读完需:约 7 分钟

优雅停机概念

  • 对一个 Java 应用,一般来说当 main 方法执行完,或者所有的非守护线程执行完,应用就自动退出了

  • 我们也可以使用 Java 里的 API System.exit(0) 强制应用退出。另外 Linux 的 kill pid 命令也能实现同样的效果。在这两种退出模式下,Java 应用会立刻关闭,对象被立刻清空、连接直接丢弃,所以会出现一些奇怪的异常,比如 IllegalStateException 之类的

  • 我们需要有一个机制,能通知到 JVM 让应用退出前,先不再接收新的消息,但正在处理的任务要正常完成,完成特定资源清理,之后才退出应用,这就是优雅关机(graceful shutdown)的概念

Java Shutdown Hook

  • Liunx 中的应用关闭

  • 我们关闭应用的时候,如果用kill pid,相当于发送了一个SIGTERM (15)-Termination 信号,默认情况下程序会忽略,此时应用会直接退出

  • 但我们也可以在代码里捕获这个信号并执行处理关机逻辑,从而实现优雅关机,这是一般实现方案

  • 而使用kill -9 pid 时,该信号无法被程序捕捉,系统会直接关闭进程,不给任何退出时间。

  • 注意,我们只能杀死我们 own 的应用,而 root 可以杀死任何应用

  • Java Shutdown Hook

  • Java 提供了一个叫关闭钩子(Shutdown Hook)的机制来实现优雅停机

  • 所谓的 Shutdown Hook,其实就是一个我们自己实现的 Thread,然后这个线程被我们通过Runtime.getRuntime().addShutdownHook(new MyShutdownHook()) 注册到了 JVM 上

  • 当 JVM 捕获了关闭信号后,会运行所有注册过的 Shutdown Hook,来执行我们的清理工作,之后再退出程序

  • 下面的是一个简单的 Shutdown Hook,运行后通过 kill pid 关闭后,会发现钩子里的日志被正常打印


public class MyShutdownHooker {    public static void main(String[] args) {        Runtime.getRuntime().addShutdownHook(eldenLordHook());        for (;;) {            System.out.println("killing killing killing");            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static Thread eldenLordHook() {        return new Thread() {            @Override            public void run() {                System.out.println("the tanished has became elden lord of the land beteen");            }        };    }}
复制代码


  • Java Shutdown Hook 分类

  • Java 里的 Shutdown Hook 有两类,JVM 级别的和应用级别的,我们通过 Runtime.getRuntime().addShutdownHook() 注册的都是应用级别的

  • 应用级别的 Shutdown Hook 会被并发执行,因此要注意线程安全性

  • 下面是应用 Shutdown Hook 的执行逻辑:


class ApplicationShutdownHooks {    ...        static void runHooks() {        Collection<Thread> threads;        synchronized(ApplicationShutdownHooks.class) {            threads = hooks.keySet();            hooks = null;        }        for (Thread hook : threads) {            hook.start();        }        for (Thread hook : threads) {            while (true) {                try {                    hook.join();                    break;                } catch (InterruptedException ignored) {                }            }        }    }}
复制代码


  • Java Shutdown Hook 如何实现关闭信号捕捉

  • 当我们使用 kill pid 关闭应用时,OS 发出一个关闭信息,这个关闭信号被应用捕捉,从而触发了后面关闭流程。那这个信号是如何被 Java 程序捕捉的哪?

  • Java 应用里的信号捕捉是通过 java.lang.Runtime 实现的,每个应用只会有一个 Runtime 实例,和底层 OS 绑定捕捉关闭信息

  • 能被捕捉的关闭信号主要有:kill pid 命令、系统关机、登录 shell 退出、CTRL + C 等

  • Shutdown Hook 注意事项

  • 程序退出时,JVM 会并发执行所有的应用 Shutdown Hook,并且只有所有 Shutdown Hook 都执行完,程序才正常退出

  • 另外,执行 Shutdown Hook 时,应该认为应用内的各种服务、资源都已经处于不可靠状态

  • 因此,编写 Shutdown Hook 时要特别小心,不要有死锁。Shutdown Hook 应该是线程安全的,且不依赖于应用资源,比如,假设你的 Shutdown Hook 依赖另一个服务,这个服务又注册了自己的 Shutdown Hook,已经先行一步清理完自己的资源,这时候你的 Shutdown Hook 就会有问题

  • 另外,Shutdown Hook 应该能很快就完成自己的工作。在关机导致的应用退出场景下,OS 只会给应用一小段时间来做自己的事情,过时就强制关闭

Spring Boot 的优雅关机方案

  • Spring Boot 2.3 为内嵌的容器增加了 graceful shutdown 功能,配置十分简单


server.shutdown=gracefulspring.lifecycle.timeout-per-shutdown-phase=1m  # 默认是 30 s
复制代码


  • Spring Boot 优雅关机流程

  • 当配置了优雅关机后,如果应用被要求关闭,Spring Boot 的内嵌容器(比如 Tomcat),会开始拒绝外部请求

  • 如果内部没有待处理的请求,应用会直接关闭

  • 如果还有在处理的请求,应用会等待请求处理完成,默认是 30 秒,如果 30 秒后还没处理完,应用仍然会关闭,你可以执行设置该时间

参考资料

发布于: 刚刚阅读数: 5
用户头像

陈德伟

关注

还未添加个人签名 2018.04.26 加入

兴趣广而不精,从小爱玩游戏还是个手残,假装爱书这么多年还是拙于下笔。从事软件行业十几年也没有什么拿的出手的成就,只能安慰自己贵在坚持。《On Java》译者之一。

评论

发布
暂无评论
Java应用的优雅停机总结_Java_陈德伟_InfoQ写作社区