java進(jìn)程關(guān)閉事件監(jiān)聽

jvm進(jìn)程如何感知關(guān)閉事件

java.lang.Shutdown

結(jié)束一個普通的java進(jìn)程,一般來說可以讓程序自行結(jié)束,也可以通過System.exit(n);來主動觸發(fā)終止。

如果是linux系統(tǒng),還可以通過外部信號來終止進(jìn)程。
一般來說停止一個服務(wù)常用的方式就是kill -2 pid(ctrl + C)、kill -9 pidkill -15 pid
kill -9 可以認(rèn)為操作系統(tǒng)從內(nèi)核級別直接強(qiáng)行kill進(jìn)程,對進(jìn)程來說沒有任何的準(zhǔn)備,且無法監(jiān)聽-9信號。
kill -2 和 -15 則是操作系統(tǒng)給該進(jìn)程發(fā)送一個信號通知,告知應(yīng)用主動關(guān)閉,應(yīng)用可以監(jiān)聽并接收到信號,可以完成一些關(guān)閉回收等動作,然后自我停止。
jvm專門有個Signal Dispatcher線程來接收信號。

Shutdown針對這幾類終止方式提供了兩個處理方法。

  /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon thread has finished.  
    Unlike the exit method, this method does not actually halt the VM. */
    static void shutdown() {
        synchronized (lock) {
            switch (state) {
            case RUNNING:       /* Initiate shutdown */
                state = HOOKS;
                break;
            case HOOKS:         /* Stall and then return */
            case FINALIZERS:
                break;
            }
        }
        synchronized (Shutdown.class) {
            sequence();
        }
    }

結(jié)合方法注釋,當(dāng)前進(jìn)程如果所有的非守護(hù)線程執(zhí)行完成,會由JNI DestroyJavaVM觸發(fā)shutdown方法調(diào)用。此方法并沒有halt(停止)VM。

  /* Invoked by Runtime.exit, which does all the security checks.
     * Also invoked by handlers for system-provided termination events, which should pass a nonzero status code.
     */
    static void exit(int status) {
        boolean runMoreFinalizers = false;
        synchronized (lock) {
            if (status != 0) runFinalizersOnExit = false;
            switch (state) {
            case RUNNING:       /* Initiate shutdown */
                state = HOOKS;
                break;
            case HOOKS:         /* Stall and halt */
                break;
            case FINALIZERS:
                if (status != 0) {
                    /* Halt immediately on nonzero status */
                    halt(status);
                } else {
                    // Compatibility with old behavior: Run more finalizers and then halt
                    runMoreFinalizers = runFinalizersOnExit;
                }
                break;
            }
        }
        if (runMoreFinalizers) {
            runAllFinalizers();
            halt(status);
        }
        synchronized (Shutdown.class) {
            //Synchronize on the class object, causing any other thread that attempts to initiate shutdown to stall indefinitely
            sequence();
            halt(status);
        }
    }

當(dāng)在代碼中調(diào)用了Runtime.exit(System.exit),會調(diào)用此exit方法,status是0表示正常退出,非0表示異常退出,此方法會主動halt VM。

除了進(jìn)程退出的處理方法外,在ShutDown類中,還定義了hook,允許我們在進(jìn)程停止前,完成一些清場的操作。

  // 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];

每個hook對應(yīng)一個線程,會在add方法中添加,在runHooks中依次執(zhí)行。

    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");
                // ... 異常場景判斷
            hooks[slot] = hook;
        }
    }
    /* Run all registered shutdown hooks */
    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;
                }
            }
        }
    }

如何添加自定義的shutdown hook

Shutdown類屬于系統(tǒng)的操作類,并沒有暴露給應(yīng)用層使用。如果我們想定義自己的shutdown hook,可以使用Runtime.getRuntime().addShutdownHook()

  public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

這個方法調(diào)用了ApplicationShutdownHooks.add(hook)

ApplicationShutdownHooks
class ApplicationShutdownHooks {
    private static IdentityHashMap<Thread, Thread> hooks;
    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;
        }
    }
    /* Add a new shutdown hook.  Checks the shutdown state and the hook itself, but does not do any security checks. */
    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在類初始化過程完成了hook回調(diào)的注冊,并初始化了IdentityHashMap,當(dāng)有自定義的hook被添加時,緩存到map中。

當(dāng)shutdown被觸發(fā)后,會通過hook回調(diào)來調(diào)用到runHooks()

    /* Iterates over all application hooks creating a new thread for each
     * to run in. Hooks are run concurrently and this method waits for
     * them to finish.
     */
    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) {
                }
            }
        }
    }

runHooks()會遍歷啟動的每一個hook線程,并通過join來等待所有hook執(zhí)行完成。因?yàn)檫@個hook線程是并行操作的,所以這里無法保證hook的執(zhí)行順序。

總結(jié)

  1. jvm進(jìn)程的關(guān)閉會由JNI觸發(fā)Shutdown類中的exit()shutdown(), 這兩個方法會調(diào)用hook回調(diào)。
  2. 自定義的shutdown hook通過Runtime.getRuntime().addShutdownHook()添加到進(jìn)程中。
  3. 多個自定義shutdown hook并行執(zhí)行,不保證執(zhí)行順序。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容