Java提供了Shutdown Hook機制,它讓我們在程式正常退出或者發生異常時能有機會做一些清場工作。使用的方法也很簡單,<code>Java.Runtime.addShutdownHook(Thread hook)</code>即可。關閉鈎子其實可以看成是一個已經初始化了的但還沒啟動的線程,當JVM關閉時會并發地執行注冊的所有關閉鈎子。
向JVM注冊關閉鈎子後的什麼時候會被調用,什麼時候不會被調用呢?分成以下情況:
Java程式正常運作完退出時會被調用。
windows和linux終端中通過ctrl-c終止指令時會被調用。
JVM發生OutOfMemory而退出時會被調用。
Java程式中執行System.exit()時會被調用。
作業系統關閉時會被調用。
linux通過kill pid(除了kill -9 pid)結束程序時會被調用。
windows直接結束程序時不會被調用。
鈎子的添加和删除都是通過 Runtime 來實作,裡面的實作也比較簡單,可以看到 addShutdownHook 和 removeShutdownHook 方法都是先通過安全管理器先檢查是否有 shutdownHooks 的權限,然後再通過 ApplicationShutdownHooks 添加和删除鈎子。
ApplicationShutdownHooks 可以看成是用來保管所有關閉鈎子的容器,而主要是通過一個 IdentityHashMap
有了 hooks 這個變量,添加删除鈎子就是直接向這個 HashMap 進行 put 和 remove 操作了,其中在操作前也會做一些檢查,比如添加鈎子前會做三個判斷:
1. 所有鈎子是否已經開始執行了,hooks 為 null 即表示所有關閉鈎子已經開始執行,此時不能再添加了。
2. 鈎子狀态是否為 alive ,是則表示鈎子已經在運作,不能添加了。
3. 是否已經包含了該鈎子,已包含則不能再添加。
類似的判斷邏輯還有 remove 操作。
而 ApplicationShutdownHooks 中真正負責啟動所有鈎子的任務由 runHooks 方法負責,它的邏輯如下:
1. 先對 ApplicationShutdownHooks 類加鎖并取到所有鈎子,然後将 hooks 變量設為 null 。
2. 周遊所有鈎子,分别啟動鈎子,前面有說到關閉鈎子其實可以看成是一個已經初始化了的但還沒啟動的線程,這裡調用 start 方法将其啟動即可。
3. 用 join 方法協調所有鈎子線程,等待他們執行完畢。
ApplicationShutdownHooks 的 runHooks 方法又是由誰負責調用的呢?如下,它其實是變成一個 Runnable 對象添加到 Shutdown 類中了,Runnable 的 run 方法負責調用 runHooks 方法。接下去就要看 Shutdown 類什麼時候執行該 Runnable 對象了。
ApplicationShutdownHooks 的 Runnable 對象添加到 Shutdown 中的邏輯如下,
slot表示将Runnable對象賦給 hooks 數組中的哪個元素中, Shutdown 中同樣有一個 hooks 變量,它是 Runnable[] 類型,長度為 MAX_SYSTEM_HOOKS ,即為 10 。這個數組可以看成是鈎子的優先級實作,數組下标用于表示優先級,slot = 1 則表示指派到數組中第二個元素。
registerShutdownInProgress 表示是否允許注冊鈎子,即使正在執行 shutdown 。前面傳入 false ,顯然是不允許。其中 state > RUNNING 條件表示其他狀态都要抛出異常,除非是 RUNNING 狀态,這個很好了解,一共有三個狀态,RUNNING、HOOKS、FINALIZERS,值分别為0、1、2。如果 registerShutdownInProgress 為 true 則隻要不為 FINALIZERS 狀态,同時 slot 也要大于目前鈎子數組的下标即可。
在前面說到的鈎子執行時機的情況下,JVM都會調用到 Shutdown 類的 sequence 方法,如下,
首先判斷目前狀态不等于 HOOKS 則直接傳回,接着執行 runHooks 方法,這個方法也是我們主要要看的方法。然後再将狀态設為 FINALIZERS ,最後如果需要的話還要調用 runAllFinalizers 方法執行所有 finalizer。是以在JVM關閉時 runHooks 方法是會被調用的。
runHooks 方法邏輯簡單,就是周遊 Runnable 數組,一個個調用其 run 方法讓其執行。
以下是廣告和相關閱讀
========廣告時間========
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/74080321">為什麼寫《Tomcat核心設計剖析》</a>
=========================
相關閱讀:
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/73743876">從JDK源碼角度看Object</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/78026810">從JDK源碼角度看Long</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/77196626">從JDK源碼角度看Integer</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/78183273">從JDK源碼角度看Float</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/77941129">volatile足以保證資料同步嗎</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/72933108">談談Java基礎資料類型</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51455094">從JDK源碼角度看并發鎖的優化</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51468764">從JDK源碼角度看線程的阻塞和喚醒</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51433204">從JDK源碼角度看并發競争的逾時</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51397266">從JDK源碼角度看java并發線程的中斷</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51371416">從JDK源碼角度看Java并發的公平性</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/51360228">從JDK源碼角度看java并發的原子性如何保證</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/74557125">從JDK源碼角度看Byte</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/73350488">從JDK源碼角度看Boolean</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/76557578">從JDK源碼角度看Short</a>
<a href="http://blog.csdn.net/wangyangzhizhou/article/details/78246035">從JDK源碼看System.exit</a>
歡迎關注:
