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!