JAVA 应用优雅关闭
Linux 信号
信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。通俗来讲,信号就是进程间的一种异步通信机制。
对于内核来说,信号的意义在于通知进程让进程知道已经发生了某个特定事件,或/和强迫进程执行它的信号处理程序,比如本文讨论的就是通过信号完成对应用的优雅关闭(也就是关闭前进行一些扫尾工作)。
对于应用程序来说,信号是异步的,有点像CPU的异步中断,本质上也是依赖中断/异常处理来实现的。(参考:中断/异常、系统调用与信号)
对信号的应答有以下三种方式:
- 显式地忽略信号
- 执行信号对应的默认操作
- 捕获信号
SIGKILL 和SIGSTOP 不能被显示地忽略和捕获,所以必须执行他们的默认操作:SIGKILL 强迫进程终止,SIGSTOP 停止进程执行。
C程序通过signal函数来捕获信号,并注册对应的处理函数
Linux kill 命令
kill 命令用于向线程组发送一个信号,其常用的信号选项如下:
- kill -2 pid 向指定 pid 发送 SIGINT 中断信号, 等同于 ctrl+c.
- kill -9 pid, 向指定 pid 发送 SIGKILL 立即终止信号.
- kill -15 pid, 向指定 pid 发送 SIGTERM 终止信号.
- kill pid 等同于 kill 15 pid
SIGINT/SIGKILL/SIGTERM 信号的区别:
- SIGINT (ctrl+c) 信号 (信号编号为 2), 信号会被当前进程树接收到, 也就说, 不仅当前进程会收到该信号, 而且它的子进程也会收到.
- SIGKILL 信号 (信号编号为 9), 程序不能捕获该信号, 最粗暴最快速结束程序的方法. (参考上一节,SIGKILL 和SIGSTOP 不能被显示地忽略和捕获)
- SIGTERM 信号 (信号编号为 15), 信号会被当前进程接收到, 但它的子进程不会收到, 如果当前进程被 kill 掉, 它的的子进程的父进程将变成 init 进程 (init 进程是那个 pid 为 1 的进程)
一般要结束某个进程, 我们应该优先使用 kill pid , 而不是 kill -9 pid. 如果对应程序提供优雅关闭机制的话, 在完全退出之前, 先可以做一些善后处理.
JAVA 原生应用
Java只支持一种信号量的捕获,即使用runtime.addShutdownHook()对退出信号做处理。按照Java标准,Java不支持其他信号的处理,因为这涉及到操作系统,而不是JVM层面的事情。另外一个原因是有的信号是JVM本身需要处理的,而不是暴露给Application
JAVA 虚拟机对于系统内核来说也是一个应用程序,本质上还是通过signal函数注册的信号处理函数,同样也绕不过操作系统的限制,即:SIGKILL 和SIGSTOP 不能被显示地忽略和捕获
但JAVA比C程序更加优雅的一点是,把shutdown hook封装到一个线程中去执行,这种优势是建立在JAVA这套完整的线程模型中的。
我们也可以在C程序中,使用signal函数注册SIGTERM的回调函数,而这个回调函数的内容是立即运行一个新的线程,在新的线程中才会真正处理这个信号
Spring boot应用
捕获容器关闭事件
Netty 应用
shutdownGracefully