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 pid吏颖、kill -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被添加時(shí)数苫,緩存到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)系作者
  • 序言:七十年代末庐椒,一起剝皮案震驚了整個濱河市蚂踊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌棱诱,老刑警劉巖迈勋,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醋粟,死亡現(xiàn)場離奇詭異,居然都是意外死亡镰官,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門宙搬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人勇垛,你說我怎么就攤上這事士鸥。” “怎么了烤礁?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長勤众。 經(jīng)常有香客問我们颜,道長,這世上最難降的妖魔是什么窥突? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任阻问,我火速辦了婚禮,結(jié)果婚禮上则拷,老公的妹妹穿的比我還像新娘煌茬。我一直安慰自己,他們只是感情好晾蜘,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布剔交。 她就那樣靜靜地躺著改衩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葫督。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天偎快,我揣著相機(jī)與錄音晒夹,去河邊找鬼。 笑死丐怯,一個胖子當(dāng)著我的面吹牛他膳,可吹牛的內(nèi)容都是我干的棕孙。 我是一名探鬼主播些膨,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼订雾,長吁一口氣:“原來是場噩夢啊……” “哼洼哎!你這毒婦竟也來了沼本?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤识补,失蹤者是張志新(化名)和其女友劉穎凭涂,沒想到半個月后贴妻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澎胡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年滤馍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熙涤。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡困檩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出等舔,到底是詐尸還是另有隱情糟趾,我是刑警寧澤甚牲,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布丈钙,位于F島的核電站交汤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏星岗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望施逾。 院中可真熱鬧汉额,春花似錦、人聲如沸怎茫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缝呕,卻和暖如春供常,著一層夾襖步出監(jiān)牢的瞬間鸡捐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工源祈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留新博,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓原献,卻偏偏與公主長得像埂淮,于是被迫代替她去往敵國和親倔撞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容