当使用 kill pid 命令来杀死一个Java进程时,该进程如何避免被杀死?即如何在Linux系统中让Java进程屏蔽"kill"命令?
最初,我尝试在面对这个问题时,尝试使用ShutdownHook注册一个钩子程序,在钩子程序中,判断当前系统是否可以退出。如果不能退出,则抛出异常,试图终止服务退出。
ShutdownHook 机制
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
//判断当前系统状态,如果不能退出,则抛出异常。
if(!exitEnable){
throw new RuntimeException("当前系统不能退出");
}
});
JVM 收到Kill 信号时,会开始执行注册的钩子程序。
然而 kill 之后,进程还是被关闭了,效果不佳,没有达到我的预期。看来 ShutdownHook 并不能屏蔽 kill 命令。
JDK在addShutdownHook方法上提供了详细的注释,我翻译一下
当虚拟机开始关闭时,会以不确定的顺序启动所有的钩子程序,它们并发执行
当所有的钩子完成后,虚拟机才会关闭。在此期间守护线程和非守护线程都会继续运行。
如果通过调用System.exit()方法关机,一旦开始执行关闭程序,只能通过System.halt()方法强制终止虚拟机。
一旦开始执行关闭程序,不得再新建钩子或注销钩子,尝试以上两个操作会导致抛出IllegalStateException。
shutdown钩子在一个微妙时刻执行,应该进行防御编码,要写的线程安全,尽量避免死锁。
shutdown钩子应该尽快完成(虚拟机需要钩子程序全部执行完成,才能关闭)。
JVM会并发执行钩子程序,即使钩子出现异常,也不会停止JVM关闭的过程。
我们需要想出其他的解决办法!有人可能会问,什么样的业务系统需要阻止Kill命令?我负责的系统是一个云计算管控系统,在虚拟机进行快照备份等操作时,流程会较长时间地运行,而且系统并没有实现无状态服务,如果此时系统重新发布,会导致数据不一致。
最佳的解决办法是尽快使流程变成无状态服务,但由于历史遗留问题的限制,解决方案会比较复杂,因此我们决定先采取一个临时方案。当系统接收到Kill命令时,首先判断是否在执行重要任务,如果服务不能重新启动,就先阻止Kill命令。
ShutdownHook无法满足这种需求,只有使用Sun公司的内置Signal工具类才能解决这个问题。
SignalHandler 机制
下面的示例代码介绍了 SignalHandler 的使用,通过注册 TERM 信号也就是 kill 命令,在该处理逻辑中,如果没有调用 System.exit(0),系统就不会退出!
public class TestKill {
public static void main(String[] args) {
Signal sigKill = new Signal("TERM");
SignalHandler handler = new SignalHandler() {
public void handle(Signal signal) {
// 忽略SIGKILL信号
System.out.println("收到 kill命令,忽略 SIGTERM signal.");
}
};
// 注册信号处理器
Signal.handle(sigKill, handler);
// 每秒打印一次
while (true) {
try {
System.out.println("The program is running.");
int time = 1111;
Thread.sleep(time);
} catch (InterruptedException e) {
}
}
}
}
演示 使用
下图的截图显示,当执行程序后,通过使用 Kill 命令来终止进程时,Java 进程收到了 TERM 信号。由于信号处理程序中仅仅打印了一行日志,并没有调用 System.exit(),因此Java 进程能够继续执行。

在使用 "kill -9 pid" 命令时,该进程将被强制关闭。无论是否使用 SignalHandler,-9 信号都无法被忽略。我尝试注册 KILL 信号,但却遇到了一个异常。
Signal already used by VM or OS: SIGKILL

总结
-
SignalHandler 可以注册信号处理程序,在该处理程序中,可以选择关闭进程,也可以不关闭进程。这种机制,给了 Java 程序员灵活控制服务何时关闭的能力!
-
ShutdownHook 它的适用场景是在进程关闭前,进行一些任务等待和资源清理工作,这服务关闭流程的一个子流程,它无法阻止服务的关闭。
当 Kill 一个进程时,Java 进程首先收到 TERM 信号,如有信号处理程序,则执行信号处理程序。如没有信号处理程序,则启动关闭流程,在关闭流程中,会并发执行 ShutdownHook 钩子程序。钩子程序执行完毕后,服务正常关闭。
切记,ShutdownHook 在 SignalHander 机制处理之后。
推荐阅读: # 面试官:如何确保服务平稳发布?
我的开源项目
最后夹带一点私货,五阳最近花了3个月的时间完成一个开源项目。
开源3周以来,已有近 230 多个关注和Fork
Gitee:gitee.com/juejinwuyan…
GitHub github.com/juejin-wuya…
开源平台上有很多在线商城系统,功能很全,很完善,关注者众多,然而实际业务场景非常复杂和多样化,开源的在线商城系统很难完全匹配实际业务,广泛的痛点是
- 功能堆砌,大部分功能用不上,需要大量裁剪;
- 逻辑差异点较多,需要大量修改;
- 功能之间耦合,难以独立替换某个功能。
由于技术中间件功能诉求较为一致,使用者无需过多定制化,技术中间件开源项目以上的痛点不明显,然而电商交易等业务系统虽然通用性较多,但各行业各产品的业务差异化极大,所以导致以上痛点比较明显
所以我在思考,有没有一个开源系统,能提供电商交易的基础能力,能让开发者搭积木的方式,快速搭建一个完全契合自己业务的新系统呢?
- 他们可以通过编排和配置选择自己需要的功能,而无需在一个现成的开源系统上进行裁剪
- 他们可以轻松的新增扩展业务的差异化逻辑,不需要阅读然后修改原有的系统代码!
- 他们可以轻松的替换掉他们认为垃圾的、多余的系统组件,而不需要考虑其他功能是否会收到影响
开发者们,可以择需选择需要的能力组件,组件中差异化的部分有插件扩展点能轻松扩展。或者能支持开发者快速的重新写一个完全适合自己的新组件然后编排注册到系统中?
memberclub 就是基于这样的想法而设计的。 它的定位是电商类交易系统工具箱, 以SDK方式对外提供通用的交易能力,能让开发者像搭积木方式,从0到1,快速构建一个新的电商交易系统!
具体介绍可参见
Gitee开源地址:gitee.com/juejinwuyan…
GitHub开源地址 : github.com/juejin-wuya…
在这个项目中你可以学习到 SpringBoot 集成 以下框架或组件。
- Mybatis、Mybatis-plus 集成多数据源
- Sharding-jdbc 多数据源分库分表
- redis/redisson 缓存
- Apollo 分布式配置中心
- Spring Cloud 微服务全家桶
- RabbitMq 消息队列
- H2 内存数据库
- Swagger + Lombok + MapStruct
同时你也可以学习到以下组件的实现原理
- 流程引擎的实现原理
- 扩展点引擎实现原理
- 分布式重试组件实现原理
- 通用日志组件实现原理 参考:juejin.cn/post/740727…
- 商品库存实现原理: 参考:juejin.cn/post/731377…
- 分布式锁组件: 参考:
- Redis Lua的使用
- Spring 上下文工具类 参考: juejin.cn/post/746927…