一、問題引入
背景
在編寫一個(gè)需要持續(xù)在后臺(tái)運(yùn)行的程序的時(shí)候遇到了這樣的場景:我的程序在主函數(shù)中創(chuàng)建了一個(gè)線程池周期性地執(zhí)行任務(wù),我希望主線程和線程池都持續(xù)運(yùn)行,但如果收到外部的關(guān)閉信號(hào)時(shí),主線程和線程池也都能同時(shí)退出。想到的就是程序結(jié)束的時(shí)候需要有一個(gè)stop()方法去手動(dòng)關(guān)閉線程池,但是怎么控制這個(gè)stop()方法在我想要的時(shí)候調(diào)用,以什么形式去接收外部的關(guān)閉信號(hào)也成了需要考慮的問題。
原始思路
最開始的嘗試是我將程序的運(yùn)行和停止分別用"start"和"stop"兩種狀態(tài)表示,然后用一個(gè)狀態(tài)文件state去記錄當(dāng)前的狀態(tài)(程序啟動(dòng)時(shí)默認(rèn)是"start"),如果想要關(guān)閉這個(gè)正在運(yùn)行的程序,就去修改狀態(tài)文件state,將里面內(nèi)容變?yōu)?quot;stop"。同時(shí)在主函數(shù)中打開這個(gè)狀態(tài)文件,循環(huán)監(jiān)聽里面的內(nèi)容,如果發(fā)現(xiàn)變?yōu)?quot;stop",就去調(diào)用stop()方法執(zhí)行關(guān)閉邏輯。按照這個(gè)思路,我寫了一個(gè)簡單的程序在IDEA中測試了一下效果,發(fā)現(xiàn)是可行的。但是當(dāng)我將程序打包,在mac系統(tǒng)上運(yùn)行jar包進(jìn)行測試的時(shí)候,不知什么原因,程序總是讀到state文件剛打開時(shí)的內(nèi)容,不能檢測到state文件的變化,無法按我設(shè)想的方式進(jìn)行關(guān)閉。因此只能另想辦法。
無意間看見JVM鉤子函數(shù)的介紹,發(fā)現(xiàn)這可能正是我想要的,于是趕緊拿來試一試。
二、JVM鉤子使用場景
JVM關(guān)閉的情況如下圖所示分為三類,第一種是正常的關(guān)閉,第二種是異常關(guān)閉的情況,第三種是強(qiáng)制關(guān)閉的情況。
JVM鉤子函數(shù)對(duì)于前兩種方式都可以進(jìn)行優(yōu)雅的關(guān)閉,但是對(duì)最后一種強(qiáng)制關(guān)閉就不起作用了。
下面我會(huì)根據(jù)這三種JVM關(guān)閉過程進(jìn)行簡單演示。
正常關(guān)閉
代碼如下:
public class TestJVMHook { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(()-> stop() )); start(); System.out.println("===程序正常結(jié)束==="); } public static void start() { System.out.println("===調(diào)用start()方法==="); } public static void stop() { System.out.println("===調(diào)用stop()方法==="); } }
運(yùn)行結(jié)果:
===調(diào)用start()方法===
===程序正常結(jié)束===
===調(diào)用stop()方法===
可以看到,在鉤子函數(shù)中聲明了stop()方法,然后程序正常結(jié)束后會(huì)自動(dòng)調(diào)用鉤子函數(shù)。
異常關(guān)閉
異常關(guān)閉分為OOM和RuntimeException兩種情況,我用除數(shù)為0的運(yùn)行時(shí)異常來演示。
代碼如下:
public class TestJVMHook { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(()-> stop() )); start(); int res = 10/0; System.out.println("===程序結(jié)束==="); } public static void start() { System.out.println("===調(diào)用start()方法==="); } public static void stop() { System.out.println("===調(diào)用stop()方法==="); } }
運(yùn)行結(jié)果:
===調(diào)用start()方法===
===調(diào)用stop()方法===
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.example.TestJVMHook.main(TestJVMHook.java:9)
可以看到執(zhí)行"10/0"時(shí)發(fā)生運(yùn)行時(shí)異常,并不會(huì)正常打印下一行語句,但仍然會(huì)自動(dòng)調(diào)用鉤子函數(shù)中stop()方法。
強(qiáng)制關(guān)閉
這里我們啟動(dòng)一個(gè)循環(huán)程序,然后手動(dòng)去關(guān)閉它。
代碼如下:
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("循環(huán)計(jì)數(shù)器"+(count++)); Thread.sleep(5*1000); } } public static void start() { System.out.println("===調(diào)用start()方法==="); } public static void stop() { System.out.println("===調(diào)用stop()方法==="); } }
啟動(dòng)后查看進(jìn)程id,然后通過"kill -9 <pid>"強(qiáng)制關(guān)閉:
運(yùn)行結(jié)果:
還是上面那段代碼,再次啟動(dòng),采用"kill <pid>"關(guān)閉:
發(fā)現(xiàn)通過"kill "正常關(guān)閉可以有效調(diào)用鉤子函數(shù),但是"kill -9 "強(qiáng)制關(guān)閉則不會(huì)調(diào)用鉤子函數(shù)。
三、回歸問題
經(jīng)過一系列測試,驗(yàn)證了JVM鉤子函數(shù)確實(shí)可以實(shí)現(xiàn)我想要的資源關(guān)閉效果。由于我的程序是一個(gè)循環(huán)程序,需要手動(dòng)關(guān)閉,因此可以在關(guān)閉程序的腳本中通過kill pid的方式進(jìn)行鉤子函數(shù)的調(diào)用。
到此這篇關(guān)于JVM鉤子函數(shù)使用場景的文章就介紹到這了,更多相關(guān)JVM鉤子函數(shù)使用內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/acelin/p/15170821.html