如何優(yōu)雅地關(guān)閉JVM?看看鉤子函數(shù)如何一步實(shí)現(xiàn)

前言

1、基本概述

程序的啟動(dòng)很簡(jiǎn)單,啟動(dòng)的時(shí)候通常會(huì)做一些預(yù)加載資源的操作。但是有時(shí)候關(guān)閉的時(shí)候,啟動(dòng)的時(shí)候預(yù)加載的資源并沒(méi)有完全清理干凈,因此可以使用鉤子函數(shù)來(lái)完成。

2、JVM關(guān)閉的場(chǎng)景分類(lèi)

直接看一張圖吧,本圖來(lái)自博客園的BarryWang,特在此說(shuō)明。

img

從上面可以看到,JVM關(guān)閉主要分為了三類(lèi),第一種是正常的關(guān)閉,第二種是異常關(guān)閉的情況,第三種是強(qiáng)制關(guān)閉的情況。對(duì)于前兩種方式我們可以使用鉤子函數(shù)優(yōu)雅的關(guān)閉,但是強(qiáng)制關(guān)閉的時(shí)候鉤子函數(shù)并不起作用。

有了這些概念,我們直接使用一個(gè)案例進(jìn)行演示,再進(jìn)行分析。

一、代碼演示鉤子函數(shù)

1、JVM正常關(guān)閉

直接看代碼吧,

public class Test {
    public void start(){
        Runtime.getRuntime().addShutdownHook(new Thread(()-> 
                System.out.println("鉤子函數(shù)被執(zhí)行,可以在這里關(guān)閉資源")
        ));
    }
    public static void main(String[] args) throws Exception{
        new Test().start();
        System.out.println("主應(yīng)用程序在執(zhí)行");
    }
}
//控制臺(tái)輸出
//主應(yīng)用程序在執(zhí)行
//鉤子函數(shù)被執(zhí)行,可以在這里關(guān)閉資源

看控制臺(tái)打印,可以發(fā)現(xiàn),主應(yīng)用程序執(zhí)行完之后就會(huì)調(diào)用鉤子函數(shù),接下來(lái)就會(huì)正式的關(guān)閉JVM。

2、異常關(guān)閉

還是直接看代碼演示,這里我們演示異常關(guān)閉的第二種OOM的情況,我們可以先設(shè)置堆的大小為20M,然后在代碼中創(chuàng)建一個(gè)500M的對(duì)象,這樣就會(huì)OOM。參數(shù)是-Xmx20M;

public class Test {
    public void start(){
        Runtime.getRuntime().addShutdownHook(new Thread(()-> 
                System.out.println("鉤子函數(shù)被執(zhí)行,可以在這里關(guān)閉資源")
        ));
    }
    public static void main(String[] args) throws Exception{
        new Test().start();
        System.out.println("主應(yīng)用程序在執(zhí)行");
        Runtime.getRuntime().halt(1);
        byte[] b = new byte[500*1024*1024];
    }
}
//控制臺(tái)輸出
//主應(yīng)用程序在執(zhí)行
//鉤子函數(shù)被執(zhí)行,可以在這里關(guān)閉資源

從控制臺(tái)可以看出,鉤子函數(shù)在異常關(guān)閉的時(shí)候依然會(huì)被調(diào)用。

3、強(qiáng)制關(guān)閉

這里我們使用Runtime.getRuntime().halt()來(lái)演示強(qiáng)勢(shì)關(guān)閉。這個(gè)方法和System.exit的區(qū)別是,System.exit會(huì)執(zhí)行鉤子函數(shù),但是Runtime.getRuntime().halt()不會(huì)。

public class Test {
    public void start(){
        Runtime.getRuntime().addShutdownHook(new Thread(()-> 
                System.out.println("鉤子函數(shù)被執(zhí)行,可以在這里關(guān)閉資源")
        ));
    }
    public static void main(String[] args) throws Exception{
        new Test().start();
        System.out.println("主應(yīng)用程序在執(zhí)行");
        Runtime.getRuntime().halt(1);
    }
}
//控制臺(tái)輸出
//主應(yīng)用程序在執(zhí)行

從上面代碼的輸出可以看出,調(diào)用了Runtime.getRuntime().halt(1)就會(huì)強(qiáng)制關(guān)閉JVM,鉤子函數(shù)來(lái)不及執(zhí)行就關(guān)閉了。而使用System.exit依然會(huì)執(zhí)行。所以一般使用System.exit來(lái)關(guān)閉JVM。

4、移除鉤子函數(shù)

上面演示了鉤子函數(shù)的作用,有時(shí)候我們想移除也比較簡(jiǎn)單。

public class Test {
    public static void main(String[] args) throws Exception{
        //new Test().start();
        Thread willNotRun = new Thread(() -> 
            System.out.println("Won't run!"));
        Runtime.getRuntime().addShutdownHook(willNotRun);
        System.out.println("主應(yīng)用程序在執(zhí)行");
        Runtime.getRuntime().removeShutdownHook(willNotRun);
    }
}
//控制臺(tái)輸出
//主應(yīng)用程序在執(zhí)行

OK,鉤子函數(shù)的基本操作就寫(xiě)到這,使用起來(lái)比較簡(jiǎn)單,不過(guò)我之前看過(guò)Spring的啟動(dòng)流程,所以又去那個(gè)啟動(dòng)流程看了一波,發(fā)現(xiàn)也使用到了鉤子函數(shù)。

二、典型應(yīng)用場(chǎng)景

1、Spring使用

Spring在關(guān)閉上下文的時(shí)候,可以使用鉤子函數(shù)來(lái)關(guān)閉殘留的資源。方法是使用ApplicationContext注冊(cè)一個(gè)鉤子函數(shù)即可。

ApplicationContext.registerShutdownHook();
//上面的這句代碼可以分析進(jìn)去看看
public void registerShutdownHook() {
    if (this.shutdownHook == null) {
      this.shutdownHook = new Thread() {
        @Override
        public void run() {
          //Spring正常關(guān)閉
          doClose();
        }
      };
      //調(diào)用鉤子函數(shù)關(guān)閉殘留資源
      Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

從源碼可以看出,Spring其實(shí)也是調(diào)用了Java的鉤子函數(shù)進(jìn)行關(guān)閉的。

2、其他使用

我在很多博客中也看到了spark和hadoop的關(guān)閉,由于我沒(méi)看過(guò)源碼,所以這里我說(shuō)一下結(jié)論,對(duì)于其他的使用場(chǎng)景,基本上也是調(diào)用了Java的鉤子函數(shù)來(lái)執(zhí)行的。

結(jié)論

在關(guān)閉JVM的時(shí)候,我們可以封裝鉤子函數(shù)去優(yōu)雅的關(guān)閉線(xiàn)程。不過(guò)在使用的時(shí)候還需要注意以下幾個(gè)方面:

1、鉤子函數(shù)本質(zhì)是個(gè)線(xiàn)程

多個(gè)鉤子會(huì)并發(fā)執(zhí)行,JVM并不保證它們的執(zhí)行順序;因此最好是在一個(gè)鉤子中執(zhí)行一系列操作。

2、鉤子中不能再新建鉤子

在關(guān)閉鉤子中,不能執(zhí)行注冊(cè)、移除鉤子的操作,否則JVM拋出 IllegalStateException。也不能使用System.exit(),前面提到System.exit()會(huì)觸發(fā)鉤子函數(shù)的執(zhí)行,但是Runtime.halt()可以,因?yàn)镽untime.halt()可以強(qiáng)制關(guān)閉。

3、鉤子里最好不要有耗時(shí)操作

鉤子函數(shù)主要用于關(guān)閉殘留資源,因此不要有一些耗時(shí)的操作。

OK,先寫(xiě)到這。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 轉(zhuǎn)自:https://blog.csdn.net/dd864140130/article/details/4915...
    王帥199207閱讀 3,230評(píng)論 1 5
  • 概述 啟動(dòng)一個(gè)服務(wù)通常很容易. 但是, 有時(shí)我們需要在停止應(yīng)用之前執(zhí)行一個(gè)鉤子函數(shù),以便優(yōu)雅的關(guān)閉我們的應(yīng)用. 本...
    bern85閱讀 539評(píng)論 0 0
  • 一,如何正確的關(guān)閉游戲服務(wù)器 1,最簡(jiǎn)單粗爆的方法 在Linux系統(tǒng)上,使用ps -aux|grep java可以...
    王廣帥閱讀 1,755評(píng)論 0 2
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,866評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,912評(píng)論 0 11

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