JVM优雅停机
介绍
最近在看公司自己封装的Dubbo组件时,看到了一段感觉笔者觉得很棒的代码,是利用了JVM的优雅停机机制,这里记录一下。
源码
public class Provider{
private final static Object MONITOR = new Object();
public void start() throws Exception{
registerHook();
doStart();
waitShutdown();
}
protected void doStart(){
// 启动加载逻辑
}
public void shutdown(){
// 关闭逻辑
}
private void registerHook(){
// 注册关闭钩子事件
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("shutdown hook!");
Provider.this.shutdown();
}
});
}
private void waitShutdown() throws Exception{
synchronized(MONITOR){
MONITOR.wait();
}
}
}
分析
上面贴出来的代码主要有2个关注点:
- wait方法会一直阻塞等待被唤醒,或者程序终止
- JVM钩子事件可以帮助我们在程序结束时完成一些关闭资源的操作
因为本次的重点是JVM钩子函数,所以接下来就来了解一下它
JVM钩子
JVM的作用
我们经常在程序终止的时候会碰到这样的情况:
-
缓存中的数据尚未持久化到磁盘中,导致数据丢失;
-
正在进行文件的write操作,没有更新完成,突然退出,导致文件损坏;
-
线程池的任务队列中尚有接收到的任务还没来得及处理,导致任务丢失;
-
数据库操作已经完成,例如账户余额更新,准备返回应答消息给客户端时,消息尚在通信线程的发送队列中排队等待发送,进程强制退出导致应答消息没有返回给客户端,客户端发起超时重试,会带来重复更新问题;
-
其它问题等…
此时我们希望程序在
正常
退出前可以做一些类似的前置动作,而jvm钩子就是Java提供给我们的接口,让我们在程序正常结束前完成一些自定义的逻辑。
如何使用
我们可以通过Runtime.getRuntime().addShutdownHook()
完成钩子函数的注入,就像这样:
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
// 业务逻辑
}
});
这样当程序正常停止
时,可以执行相应的逻辑,例如:关闭连接池,线程池,处理未完成的业务,等等。从而达到优雅停机的目的。
钩子函数一定会执行吗
细心的读者可能已经发现,我在描述钩子函数的执行时机时,一直强调的是程序正常停止
那么什么是正常停止呢?
此图来自于JVM安全退出, 我想上面的图已经表达清楚了什么是正常的关闭程序,所以我们在编写停机脚本时应带采用kill -TERM
而非kill -9
去杀死程序,从而让我们的钩子函数得以执行。