Java虚拟机为开发人员提供了一个回调入口,开发人员通过这个回调入口,向Java虚拟机注册任意一段代码,让Java虚拟机在将要结束运行之前,执行这段代码。这个机制通常被用来做一些资源的清理工作,tomcat容器源码中就有用到这个机制,现在对这个机制做一个介绍。
demo
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
System.out.println("The hook is executing");
}
});
System.out.println("The application is going to be stopped");
}
但是使用Shutdown Hook时,会有一些注意的地方:
在某些情况下向JVM注册的hooks不会被执行
JVM并不保证这些hooks一定会被执行,hooks是否会被执行受系统内部及外部因素影响。- JVM遇到严重的内部错误的时候
- 用户通过操作系统向Java虚拟机进程发送SIGKILL,要求操作系统立即停止虚拟机进程时
- 调用
Runtime.halt()
停止虚拟机执行
只有当JVM正常退出时,hooks才会被执行,应用程序抛出的运行时异常,以及JVM抛出的错误(Error)不会影响hook的执行。
Shutdown Hook处于执行状态时,仍有可能被强制停止
一旦hooks启动之后,仍然可能由于各种原因被停止,需要注意的是,不能在hooks中进行一些非常耗时的操作,以及资源申请操作,因为这样会造成严重问题,如死锁(JVM不会保证hooks的执行顺序,如果hooks中的代码缺少足够的同步,会造成死锁,及不一致)- JVM不保证Shutdown Hooks的执行顺序
- Shutdown Hooks一旦开始执行,只能通过
Runtime.halt()
命令来停止hooks的执行,调用System.exit()
并不能停止hooks的执行 - 如果Shutdown Hook中抛出了异常,并且没有被捕获,那么JVM会使用default exception handler来处理异常
tomcat中Shutdown hook的应用
tomcat在启动容器之后,会向JVM注册一个Shutdown hook用于确保tomcat应用即使是在异常退出之后,仍然能够保证资源被正确的释放掉,下面是org.apache.catalina.startup.Catalina
类的start()
方法的片段
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
下面是CatalinaShutdownHook的定义:
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
可以看到tomcat使用Shutdown Hook来确保,Server和LoggerManager正常关闭,为了避免因Server关闭先于LoggerManager导致日志消息丢失的问题,LoggerManager并没有直接使用Shutdown Hook来关闭。