通過(guò)JDK源碼分析關(guān)閉鉤子詳解
關(guān)閉鉤子
用戶關(guān)閉關(guān)閉程序,需要做一些善后的清理工作,但問(wèn)題是,某些用戶不會(huì)按照推薦的方法關(guān)閉應(yīng)用程序,肯能導(dǎo)致善后工作無(wú)法進(jìn)行。像tomcat調(diào)用server的start方法啟動(dòng)容器,然后會(huì)逐級(jí)調(diào)用start。當(dāng)發(fā)出關(guān)閉命令是會(huì)啟動(dòng)關(guān)閉功能,但是關(guān)閉可能會(huì)有一些意外產(chǎn)生,導(dǎo)致應(yīng)用程序沒(méi)有進(jìn)入到我們制定的關(guān)閉方法去。如何解決這個(gè)問(wèn)題呢,使得即使有意外也能正常進(jìn)入關(guān)閉流程。
好在java提供了一種優(yōu)雅的方式去解決這種問(wèn)題。使得關(guān)閉的善后處理的代碼能執(zhí)行。java的關(guān)閉鉤子能確保總是執(zhí)行,無(wú)論用戶如何終止應(yīng)用程序。除非用戶kill,這個(gè)是個(gè)死穴。
Java提供了Shutdown Hook機(jī)制,它讓我們?cè)诔绦蛘M顺龌蛘甙l(fā)生異常時(shí)能有機(jī)會(huì)做一些清場(chǎng)工作。使用的方法也很簡(jiǎn)單,Java.Runtime.addShutdownHook(Thread hook)即可。關(guān)閉鉤子其實(shí)可以看成是一個(gè)已經(jīng)初始化了的但還沒(méi)啟動(dòng)的線程,當(dāng)JVM關(guān)閉時(shí)會(huì)并發(fā)地執(zhí)行注冊(cè)的所有關(guān)閉鉤子。
對(duì)java而言,虛擬機(jī)會(huì)對(duì)以下幾種操作進(jìn)行關(guān)閉:
(1)系統(tǒng)調(diào)用System.exit()方法
(2)程序最后一個(gè)守護(hù)線程退出時(shí),應(yīng)用程序正常退出。
(3)用戶強(qiáng)行中斷程序運(yùn)行,比如ctrl+c等其他方式中斷java程序
關(guān)閉鉤子的生成:
1.創(chuàng)建Thread的子類
2.實(shí)現(xiàn)run方法,應(yīng)用程序關(guān)閉時(shí)會(huì)調(diào)用該方法,不需要調(diào)用start方法
3.在應(yīng)用中實(shí)例化關(guān)閉鉤子類
4.使用Runtime注冊(cè)關(guān)閉鉤子
鉤子執(zhí)行時(shí)機(jī)
向JVM注冊(cè)關(guān)閉鉤子后的什么時(shí)候會(huì)被調(diào)用,什么時(shí)候不會(huì)被調(diào)用呢?分成以下情況:
- Java程序正常運(yùn)行完退出時(shí)會(huì)被調(diào)用。
- windows和linux終端中通過(guò)ctrl-c終止命令時(shí)會(huì)被調(diào)用。
- JVM發(fā)生OutOfMemory而退出時(shí)會(huì)被調(diào)用。
- Java程序中執(zhí)行
System.exit()時(shí)會(huì)被調(diào)用。 - 操作系統(tǒng)關(guān)閉時(shí)會(huì)被調(diào)用。
- linux通過(guò)
kill pid(除了kill -9 pid)結(jié)束進(jìn)程時(shí)會(huì)被調(diào)用。 - windows直接結(jié)束進(jìn)程時(shí)不會(huì)被調(diào)用。
添加刪除鉤子
鉤子的添加和刪除都是通過(guò) Runtime 來(lái)實(shí)現(xiàn),里面的實(shí)現(xiàn)也比較簡(jiǎn)單,可以看到 addShutdownHook 和 removeShutdownHook 方法都是先通過(guò)安全管理器先檢查是否有 shutdownHooks 的權(quán)限,然后再通過(guò) ApplicationShutdownHooks 添加和刪除鉤子。
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
public boolean removeShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
return ApplicationShutdownHooks.remove(hook);
}
ApplicationShutdownHooks保管鉤子
ApplicationShutdownHooks 可以看成是用來(lái)保管所有關(guān)閉鉤子的容器,而主要是通過(guò)一個(gè) IdentityHashMap
private static IdentityHashMap<Thread, Thread> hooks;
有了 hooks 這個(gè)變量,添加刪除鉤子就是直接向這個(gè) HashMap 進(jìn)行 put 和 remove 操作了,其中在操作前也會(huì)做一些檢查,比如添加鉤子前會(huì)做三個(gè)判斷:
1. 所有鉤子是否已經(jīng)開始執(zhí)行了,hooks 為 null 即表示所有關(guān)閉鉤子已經(jīng)開始執(zhí)行,此時(shí)不能再添加了。
2. 鉤子狀態(tài)是否為 alive ,是則表示鉤子已經(jīng)在運(yùn)行,不能添加了。
3. 是否已經(jīng)包含了該鉤子,已包含則不能再添加。
類似的判斷邏輯還有 remove 操作。
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);
}
static synchronized boolean remove(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress");
if (hook == null)
throw new NullPointerException();
return hooks.remove(hook) != null;
}
而 ApplicationShutdownHooks 中真正負(fù)責(zé)啟動(dòng)所有鉤子的任務(wù)由 runHooks 方法負(fù)責(zé),它的邏輯如下:
1. 先對(duì) ApplicationShutdownHooks 類加鎖并取到所有鉤子,然后將 hooks 變量設(shè)為 null 。
2. 遍歷所有鉤子,分別啟動(dòng)鉤子,前面有說(shuō)到關(guān)閉鉤子其實(shí)可以看成是一個(gè)已經(jīng)初始化了的但還沒(méi)啟動(dòng)的線程,這里調(diào)用 start 方法將其啟動(dòng)即可。
3. 用 join 方法協(xié)調(diào)所有鉤子線程,等待他們執(zhí)行完畢。
static void runHooks() {
Collection<Thread> threads;
synchronized(ApplicationShutdownHooks.class) {
threads = hooks.keySet();
hooks = null;
}
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
try {
hook.join();
} catch (InterruptedException x) { }
}
}
ApplicationShutdownHooks 的 runHooks 方法又是由誰(shuí)負(fù)責(zé)調(diào)用的呢?如下,它其實(shí)是變成一個(gè) Runnable 對(duì)象添加到 Shutdown 類中了,Runnable 的 run 方法負(fù)責(zé)調(diào)用 runHooks 方法。接下去就要看 Shutdown 類什么時(shí)候執(zhí)行該 Runnable 對(duì)象了。
Shutdown.add(1 , false ,
new Runnable() {
public void run() {
runHooks();
}
}
);
Shutdown中的鉤子
ApplicationShutdownHooks 的 Runnable 對(duì)象添加到 Shutdown 中的邏輯如下,
private static final int RUNNING = 0;
private static final int HOOKS = 1;
private static final int FINALIZERS = 2;
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;
}
}
slot表示將Runnable對(duì)象賦給 hooks 數(shù)組中的哪個(gè)元素中, Shutdown 中同樣有一個(gè) hooks 變量,它是 Runnable[] 類型,長(zhǎng)度為 MAX_SYSTEM_HOOKS ,即為 10 。這個(gè)數(shù)組可以看成是鉤子的優(yōu)先級(jí)實(shí)現(xiàn),數(shù)組下標(biāo)用于表示優(yōu)先級(jí),slot = 1 則表示賦值到數(shù)組中第二個(gè)元素。
registerShutdownInProgress 表示是否允許注冊(cè)鉤子,即使正在執(zhí)行 shutdown 。前面?zhèn)魅?false ,顯然是不允許。其中 state > RUNNING 條件表示其他狀態(tài)都要拋出異常,除非是 RUNNING 狀態(tài),這個(gè)很好理解,一共有三個(gè)狀態(tài),RUNNING、HOOKS、FINALIZERS,值分別為0、1、2。如果 registerShutdownInProgress 為 true 則只要不為 FINALIZERS 狀態(tài),同時(shí) slot 也要大于當(dāng)前鉤子數(shù)組的下標(biāo)即可。
在前面說(shuō)到的鉤子執(zhí)行時(shí)機(jī)的情況下,JVM都會(huì)調(diào)用到 Shutdown 類的 sequence 方法,如下,
private static void sequence() {
synchronized (lock) {
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) {
currentRunningHook = i;
hook = hooks[i];
}
if (hook != null) hook.run();
} catch(Throwable t) {
if (t instanceof ThreadDeath) {
ThreadDeath td = (ThreadDeath)t;
throw td;
}
}
}
}
首先判斷當(dāng)前狀態(tài)不等于 HOOKS 則直接返回,接著執(zhí)行 runHooks 方法,這個(gè)方法也是我們主要要看的方法。然后再將狀態(tài)設(shè)為 FINALIZERS ,最后如果需要的話還要調(diào)用 runAllFinalizers 方法執(zhí)行所有 finalizer。所以在JVM關(guān)閉時(shí) runHooks 方法是會(huì)被調(diào)用的。
runHooks 方法邏輯簡(jiǎn)單,就是遍歷 Runnable 數(shù)組,一個(gè)個(gè)調(diào)用其 run 方法讓其執(zhí)行。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
spring cloud將spring boot服務(wù)注冊(cè)到Eureka Server上的方法
本篇文章主要介紹了spring cloud將spring boot服務(wù)注冊(cè)到Eureka Server上的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
詳解Spring Cloud Zuul網(wǎng)關(guān)修改為短連接方法
本文主要介紹了詳解Spring Cloud Zuul網(wǎng)關(guān)修改為短連接方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
idea快速搭建springboot項(xiàng)目的操作方法
下面小編就為大家分享一篇idea快速搭建springboot項(xiàng)目的操作方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
spring?aop?Pointcut?execution規(guī)則介紹
這篇文章主要介紹了spring?aop?Pointcut?execution規(guī)則,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
使用Spring CROS解決項(xiàng)目中的跨域問(wèn)題詳解
這篇文章主要介紹了使用Spring CROS解決項(xiàng)目中的跨域問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java數(shù)據(jù)封裝樹形結(jié)構(gòu)代碼實(shí)例
這篇文章主要介紹了Java數(shù)據(jù)封裝樹形結(jié)構(gòu)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01
SpringSecurity詳解整合JWT實(shí)現(xiàn)全過(guò)程
JWT作為一個(gè)開放的標(biāo)準(zhǔn)(?RFC?7519?),定義了一種簡(jiǎn)潔的,自包含的方法用于通信雙方之間以Json對(duì)象的形式安全的傳遞信息。接下來(lái)通過(guò)本文給大家介紹springSecurity+jwt實(shí)現(xiàn)互踢功能,需要的朋友可以參考下2022-07-07
SpringMVC對(duì)自定義controller入?yún)㈩A(yù)處理方式
這篇文章主要介紹了SpringMVC對(duì)自定義controller入?yún)㈩A(yù)處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

