- 鉤子作用是啥
當你怕退出jvm時中斷應用正在處理的任務,從而導致各種問題時。此時鉤子就派上了用場。當然你直接拔電源、kill -9再牛逼的機制也不管用了。 - 怎麼用
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hook shut down");
}
}));
System.out.println("123");
}
運行這段代碼輸出
123
hook shut down
- 啥原理
首先,將我們新實例化的線程作爲參數調用ApplicationShutdownHooks的add方法
public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
ApplicationShutdownHooks.add(hook);
}
再來看add方法中經過一些校驗之後將其放到map中,那麼這個hooks的map怎麼來的
private static IdentityHashMap<Thread, Thread> hooks;
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代碼塊首先運行,肯定就回創建map對象,此時在static中又調用了 Shutdown的add方法,我們再繼續看
ApplicationShutdownHooks類
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;
}
}
會將ApplicationShutdownHooks類實例化的Runnable添加到Shutdown類的hooks中。
Shutdown類
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;
}
}
其次,經過以上步驟已經將鉤子添加了,那麼當jvm退出時是怎麼觸發的。
當我們運行上述代碼時在
java.lang.ApplicationShutdownHooks#runHooks中打斷點,然後如下圖,
此時我們的main線程已退出,一個叫DestroyJavaVM的線程被激活,那麼這個DestroyJavaVM是怎麼被激活的呢。
《Java性能優化權威指南》中有如下一段解釋,而上圖中的調用棧也清晰的說明了調用順序。
最後,經過以上步驟,我們添加的hook被調用,完成了退出jvm時優雅的關閉服務。