想象一下, 如果你现在刚好在 Word 上写需求文档, 电脑突然重启. 等待开机完成, 你可能会发现写了一个小时文档没有保存, 就这么没了...
一个正在运行 Java 应用如果突然将其停止, 影响不止数据丢失, 还会造成其他影响. 比如:
请求丢失: 内存队列中等待执行请求丢失
数据丢失: 处于内存缓存中数据未持久化到磁盘
文件损坏: 正在写的文件没有没有更新完成, 导致文件损坏
业务中断: 处理一半的业务被强行中断, 如支付成功了, 却没有更新到数据库中
服务未下线: 上游服务依然往停止节点发送请求
所以在关闭服务之前, 我们需要先做好善后工作, 比如保存数据, 清理资源, 下线服务, 然后才退出应用. 这种有计划平滑的关闭应用相对直接停止应用, 就显得非常『优雅』.
ps: 仔细品味, 优雅停机这个词真好~
ShutdownHook
Java 语言提供一种 ShutdownHook(钩子) 进制, 当 JVM 接受到系统的关闭通知之后, 调用 ShutdownHook 内的方法, 用以完成清理操作, 从而平滑的退出应用.
ShutdownHook 代码如下:
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- System.out.println("关闭应用, 释放资源");
- }));
Runtime.getRuntime().addShutdownHook(Thread) 需要传入一个线程对象, 后续动作将会在该异步线程内完成. 除了主动关闭应用 (使用 kill -15 指令), 以下场景也将会触发 ShutdownHook :
代码执行结束, JVM 正常退出
应用代码中调用 System#exit 方法
应用中发生 OOM 错误, 导致 JVM 关闭
终端中使用 Ctrl+C(非后台运行)
目前很多开源框架都是基于这个机制实现优雅停机, 比如 Dubbo,Spring 等.
相关注意点
ShutdownHook 代码实现起来相对简单, 但是我们还是需要小心下面这些坑.
Runtime.getRuntime().addShutdownHook(Thread) 可以被多次调用
我们可以多次调用 Runtime.getRuntime().addShutdownHook(Thread) 方法, 从而增加多个. 但是需要注意的是, 多个 ShutdownHook 之间并无任何顺序, Java 并不会按照加入顺序执行, 反而将会并发执行.
所以尽量在一个 ShutdownHook 完成所有操作.
ShutdownHook 需要尽快执行结束
不要在 ShutdownHook 执行需要被阻塞代码, 如 I/0 读写, 这样就会导致应用短时间不能被关闭.
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- while (true){
- System.out.println("关闭应用, 释放资源");
- }
- }));
上面代码中, 我们使用 while(true) 模拟长时间阻塞这种极端情况, 关闭该应用时, 应用将会一直阻塞在 while 代码中, 导致应用没办法被关闭.
除了阻塞之外, 还需要小心其他会让线程阻塞的行为, 比如死锁.
为了避免 ShutdownHook 线程被长时间阻塞, 我们可以引入超时进制. 如果等待一定时间之后, ShutdownHook 还未完成, 由脚本直接调用 kill -9 强制退出或者 ShutdownHook 代码中引入超时进制.
来源: http://www.bubuko.com/infodetail-3236099.html