今天折騰了半天才回到家,寫篇簡單的,然後趁早洗洗睡吧。
在Java程序退出時——尤其是非正常退出時,我們可能需要先執行一些善後工作,如關閉線程池、連接池、文件句柄等,即所謂“優雅停機”(graceful shutdown)。如何保證善後工作的代碼能夠被執行到呢?Java爲用戶提供了關閉鉤子(shutdown hook),它在以下情景都會被調用:
- 程序正常退出,即最後一個非守護線程結束時;
- 程序中執行到了System.exit()方法;
- 終端接收到了CTRL-C中斷,或者註銷登錄;
- 通過
kill
命令殺死進程(但是kill -9
除外)。
關閉鉤子在以下情景不會被調用:
- 通過
kill -9
命令殺死進程——所以kill -9
一定要慎用; - 程序中執行到了Runtime.getRuntime().halt()方法;
- 操作系統突然崩潰,或機器掉電。
用戶通過Runtime.getRuntime().addShutdownHook()方法來註冊關閉鉤子,示例:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
someThreadPool.shutdown();
someConnectionPool.close();
LOGGER.info("Shutdown hook called");
}));
可見,關閉鉤子的本質就是已經初始化但在JVM關閉之前最後一刻纔會執行的線程。當然,Java也提供了removeShutdownHook()方法來刪除關閉鉤子。
JDK內是通過ApplicationShutdownHooks類來維護關閉鉤子的,該類中有一個IdentityHashMap容器。
private static IdentityHashMap<Thread, Thread> hooks;
addShutdownHook()方法實際上是代理了ApplicationShutdownHooks.add()方法。在註冊關閉鉤子之前,會先判斷是否符合以下三個條件,如果是,則鉤子無法註冊:
- JVM正在關閉,即鉤子已經被觸發(此時IdentityHashMap爲null);
- 當前關閉鉤子正在執行;
- IdentityHashMap中已經存在了要註冊的鉤子。
static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered");
hooks.put(hook, hook);
}
啓動鉤子的方法則是ApplicationShutdownHooks.runHooks()。
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) {
}
}
}
}
這裏要注意Thread.join()方法的語義,即掛起調用線程,等待被調用線程(在這裏就是鉤子線程)執行完畢之後,調用線程才繼續執行。也就是說,這裏必須保證關閉鉤子在主線程真正關閉之前執行完畢。
那麼runHooks()方法又是在哪裏被調用的呢?由ApplicationShutdownHooks方法的static代碼塊可知是在Shutdown類,但是仍然包裝成了Runnable,沒有立即執行。
static {
try {
Shutdown.add(1 /* shutdown hook invocation order */,
false /* not registered if shutdown in progress */,
new Runnable() {
public void run() {
runHooks();
}
}
);
hooks = new IdentityHashMap<>();
} catch (IllegalStateException e) {
// application shutdown hooks cannot be added if
// shutdown is in progress.
hooks = null;
}
}
Shutdown類內用Runnable的數組hooks維護關閉鉤子的執行,並且該數組同時表示關閉鉤子的優先級,排在前面slot的會先執行。雖然該數組的長度爲10,但是目前只用了3個slot,用戶註冊的應用關閉鉤子的優先級夾在兩種系統鉤子的中間(即固定佔用slot 1)。
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
private static int state = RUNNING;
// The system shutdown hooks are registered with a predefined slot.
// The list of shutdown hooks is as follows:
// (0) Console restore hook
// (1) Application hooks
// (2) DeleteOnExit hook
private static final int MAX_SYSTEM_HOOKS = 10;
private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];
static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
synchronized (lock) {
if (hooks[slot] != null)
throw new InternalError("Shutdown hook at slot " + slot + " already registered");
if (!registerShutdownInProgress) {
if (state > RUNNING)
throw new IllegalStateException("Shutdown in progress");
} else {
if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
throw new IllegalStateException("Shutdown in progress");
}
hooks[slot] = hook;
}
}
registerShutdownInProgress表示是否允許在關閉過程中註冊鉤子,前面傳入的是false。如果爲true的話,則可以在當前運行的鉤子後面註冊優先級更低的鉤子。
加入到hooks數組的鉤子最終會在sequence()方法觸發執行,最後還會根據runFinalizersOnExit標誌位來判斷是否需要執行finalizer。runAllFinalizers()是一個native方法。
private static void sequence() {
synchronized (lock) {
/* Guard against the possibility of a daemon thread invoking exit
* after DestroyJavaVM initiates the shutdown sequence
*/
if (state != HOOKS) return;
}
runHooks();
boolean rfoe;
synchronized (lock) {
state = FINALIZERS;
rfoe = runFinalizersOnExit;
}
if (rfoe) runAllFinalizers();
}
private static void runHooks() {
for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
try {
Runnable hook;
synchronized (lock) {
// acquire the lock to make sure the hook registered during
// shutdown is visible here.
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
就醬吧,民那晚安晚安。