淺析java的退出鉤子(Hook)

  • 鉤子作用是啥
    當你怕退出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時優雅的關閉服務。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章