JVM鉤子函數的使用場景詳解

一、問題引入

背景

在編寫一個需要持續在後臺運行的程序的時候遇到瞭這樣的場景:我的程序在主函數中創建瞭一個線程池周期性地執行任務,我希望主線程和線程池都持續運行,但如果收到外部的關閉信號時,主線程和線程池也都能同時退出。想到的就是程序結束的時候需要有一個stop()方法去手動關閉線程池,但是怎麼控制這個stop()方法在我想要的時候調用,以什麼形式去接收外部的關閉信號也成瞭需要考慮的問題。

原始思路

最開始的嘗試是我將程序的運行和停止分別用”start”和”stop”兩種狀態表示,然後用一個狀態文件state去記錄當前的狀態(程序啟動時默認是”start”),如果想要關閉這個正在運行的程序,就去修改狀態文件state,將裡面內容變為”stop”。同時在主函數中打開這個狀態文件,循環監聽裡面的內容,如果發現變為”stop”,就去調用stop()方法執行關閉邏輯。按照這個思路,我寫瞭一個簡單的程序在IDEA中測試瞭一下效果,發現是可行的。但是當我將程序打包,在mac系統上運行jar包進行測試的時候,不知什麼原因,程序總是讀到state文件剛打開時的內容,不能檢測到state文件的變化,無法按我設想的方式進行關閉。因此隻能另想辦法。

無意間看見JVM鉤子函數的介紹,發現這可能正是我想要的,於是趕緊拿來試一試。

二、JVM鉤子使用場景

JVM關閉的情況如下圖所示分為三類,第一種是正常的關閉,第二種是異常關閉的情況,第三種是強制關閉的情況。

JVM鉤子函數對於前兩種方式都可以進行優雅的關閉,但是對最後一種強制關閉就不起作用瞭。

下面我會根據這三種JVM關閉過程進行簡單演示。

正常關閉

代碼如下:

public class TestJVMHook {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(()->
                stop()
        ));
        start();
        System.out.println("===程序正常結束===");
    }
    public static void start() {
        System.out.println("===調用start()方法===");
    }

    public static void stop() {
        System.out.println("===調用stop()方法===");
    }
}

運行結果:

===調用start()方法===
===程序正常結束===
===調用stop()方法===

可以看到,在鉤子函數中聲明瞭stop()方法,然後程序正常結束後會自動調用鉤子函數。

異常關閉

異常關閉分為OOM和RuntimeException兩種情況,我用除數為0的運行時異常來演示。

代碼如下:

public class TestJVMHook {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(()->
                stop()
        ));
        start();
        int res = 10/0;
        System.out.println("===程序結束===");
    }
    public static void start() {
        System.out.println("===調用start()方法===");
    }

    public static void stop() {
        System.out.println("===調用stop()方法===");
    }
}

運行結果:

===調用start()方法===
===調用stop()方法===
Exception in thread “main” java.lang.ArithmeticException: / by zero
 at com.example.TestJVMHook.main(TestJVMHook.java:9)

可以看到執行”10/0″時發生運行時異常,並不會正常打印下一行語句,但仍然會自動調用鉤子函數中stop()方法。

強制關閉

這裡我們啟動一個循環程序,然後手動去關閉它。

代碼如下:

public class TestJVMHook {
    public static void main(String[] args) throws InterruptedException {
        Runtime.getRuntime().addShutdownHook(new Thread(()->
                stop()
        ));
        int count = 1;
        start();
        while(true){
            System.out.println("循環計數器"+(count++));
            Thread.sleep(5*1000);
        }
    }
    public static void start() {
        System.out.println("===調用start()方法===");
    }

    public static void stop() {
        System.out.println("===調用stop()方法===");
    }
}

啟動後查看進程id,然後通過”kill -9 <pid>”強制關閉:

運行結果:

還是上面那段代碼,再次啟動,采用”kill <pid>”關閉:

發現通過”kill “正常關閉可以有效調用鉤子函數,但是”kill -9 “強制關閉則不會調用鉤子函數。

三、回歸問題

經過一系列測試,驗證瞭JVM鉤子函數確實可以實現我想要的資源關閉效果。由於我的程序是一個循環程序,需要手動關閉,因此可以在關閉程序的腳本中通過kill pid的方式進行鉤子函數的調用。

到此這篇關於JVM鉤子函數使用場景的文章就介紹到這瞭,更多相關JVM鉤子函數使用內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: