(十一)How Tomcat Works - Tomcat ShutdownHook
Tomcat启动后应该使用shutdown.sh
脚本,给Server发送”SHUTDOWN”进行关闭,完成Tomcat lifecycle的stop阶段。但是如果用户直接强制退出了,还能执行清理阶段吗?
ShutdownHook
Java有关闭机制。在jvm退出(只有守护线程、或者Linux命令行Ctrl+C、或者Windows直接叉掉窗口)时,会调用注册在jvm里的shutdown hook。所以只要把关闭Server的行为添加到shutdown hook里就行了。
添加shutdown hook非常简单:
- 获取全局Runtime对象:
Runtime.getRuntime()
; - 调用ShutdownHook方法:
Runtime.getRuntime().addShutdownHook(Thread hook)
;
所谓的hook,其实就是一个Thread对象。它记录着要做的事情(run),jvm在结束前会执行它一下。
并行执行(hook.start()
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Iterates over all application hooks creating a new thread for each
* to run in. Hooks are run concurrently and this method waits for
* them to finish.
*/
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}
}
使用hook.join()
等待所有hook执行完。
Catalina注册shutdownhook
清理Server残局的shutdown hook就一句话:server.stop()
,然后按照Tomcat的lifecycle机制,一串子组件都依次关闭了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected class CatalinaShutdownHook extends Thread {
public void run() {
if (server != null) {
try {
((Lifecycle) server).stop();
} catch (LifecycleException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
}
}
有趣的是Catalina注册该shutdown hook的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Thread shutdownHook = new CatalinaShutdownHook();
// Start the new server
if (server instanceof Lifecycle) {
try {
server.initialize();
((Lifecycle) server).start();
try {
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(shutdownHook);
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Wait for the server to be told to shut down
server.await();
} catch (LifecycleException e) {
System.out.println("Catalina.start: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
// Shut down the server
if (server instanceof Lifecycle) {
try {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
Runtime.getRuntime().removeShutdownHook(shutdownHook);
} catch (Throwable t) {
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
((Lifecycle) server).stop();
} catch (LifecycleException e) {
System.out.println("Catalina.stop: " + e);
e.printStackTrace(System.out);
if (e.getThrowable() != null) {
System.out.println("----- Root Cause -----");
e.getThrowable().printStackTrace(System.out);
}
}
}
启动Server后,start server,紧接着注册stop Server的shutdown hook。
但是如果Server收到”SHUTDOWN”了(Server在8005端口阻塞接收消息,跟Connector在8080端口无关),await
方法会结束while true循环。代码继续往后执行,这时候Server是正常结束的,要正常执行server.stop()
。就不需要在stop Server的shutdown hook里再做一遍stop server操作了,所以这里会把该shutdown hook删掉。细致!
本文由作者按照 CC BY 4.0 进行授权