JVM應用優(yōu)雅上下線,再也不擔心抖動了
一、前言
JVM的關閉方式可以分為三種:
1.正常關閉:當最后一個非守護線程結束、或者調用了System.exit、或者通過其他特定平臺的方法關閉(發(fā)送SIGINT,SIGTERM信號等)
2.強制關閉:通過調用Runtime.halt方法、或者是在操作系統(tǒng)中直接kill(發(fā)送SIGKILL信號)掉JVM進程
3.異常關閉:運行中遇到RuntimeException異常、OOM錯誤等。
二、ShutdownHook
通常JVM可使用runtime.addShutdownHook()對退出信號做處理,它讓我們在程序正常退出或者發(fā)生異常時能有機會做一些清場工作。關閉鉤子其實可以看成是一個已經初始化了的但還沒啟動的線程,當JVM關閉時會并發(fā)無序地執(zhí)行注冊的所有關閉鉤子。
Runtime.getRuntime().addShutdownHook(handleThread); //handleThread是信號處理線程。
ShutdownHook響應的信號如下:
- 1(SIGHUP):如果使用了nohup則不響應;
- 2(SIGINT):如果使用了后臺&則不響應;
- 15(SIGTERM):都響應。
注意事項:
- 不要使用kill -9來結束進程,這樣ShutdownHook得不到執(zhí)行;
- ShutdownHook要盡量短。計算機在關機前,會給所有的進程發(fā)送一個SIGTERM信號,等若干秒后就直接發(fā)送SIGKILL了;
- ShutdownHook要保證線程安全。如果多次發(fā)送信號,那么ShutdownHook被不同的線程多次執(zhí)行。
三、SignalHandler
用戶可以自定義SignalHander對特定信號進行處理。
class MySignalHandler implements SignalHandler
{
public static void listenTo(String name) {
Signal signal = new Signal(name);
Signal.handle(signal, new MySignalHandler());
}
public void handle(Signal signal) {
System.out.println("Signal: " + signal);
if (signal.toString().trim().equals("SIGTERM")) {
System.out.println("SIGTERM raised, terminating...");
System.exit(1);
}
}
}
Java對每個信號都啟動一個線程進行處理。注冊TERM信號,就啟動"SIGTERM handler" 線程。即便主線程被阻塞,信號依然可以得到處理。由于對信號的處理是多線程的,所以應保證信號處理實例SignalHandler應該是線程安全的。
四、總結
- ShutdownHook只響應1(SIGHUP)、2(SIGINT)、15(SIGTERM)三種信號,而JVM一般用nohup...&的方式啟動,所以會忽略1、2兩種信號;
- ShutdownHook觸發(fā)時,多個鉤子會并發(fā)無序執(zhí)行。如果資源關閉上有先后依賴則會有問題;
4.1 優(yōu)雅關閉
由于ShutdownHook的并發(fā)無序執(zhí)行,所以我們在優(yōu)雅關閉時不能直接kill -15,比如有殘留請求的情況,如果部分資源已關閉,那么殘留請求的執(zhí)行會有異常。 正確流程如下:
- kill -12:等待10s。用戶自定義SignalHandler處理12信號,而且此時所有的資源都是正常狀態(tài)。1)告知上游該服務已關閉,不要再發(fā)請求;2)處理殘留的請求;3)其他需要正常關閉的操作。
- kill -15:等待10s。這時會并發(fā)無序執(zhí)行注冊的ShutdownHook,進行一些資源的釋放,很有可能不需要10sJVM就退出了。
- kill -9:如果kill -15還沒有終止JVM,則直接強制退出。
這里的優(yōu)雅就體現在第一步的10秒kill -12,在資源都正常的情況下給業(yè)務一些時間來正常關閉服務。
4.2 示例
我們以轉轉的RPC框架ZZSCF為例,來看其是如何實現優(yōu)雅關閉的。
4.2.1 kill -12
首先,我們進行kil -12并等待10秒,用戶自定義SignalHandler來處理12信號,而且此時所有的資源都是正常狀態(tài)。
圖片
圖片
圖片
圖片
圖片
圖片
4.2.2 kill -15
接著,我們進行kil -15并等待10秒。這時會并發(fā)無序執(zhí)行注冊的ShutdownHook,進行一些資源的釋放,很有可能不需要10sJVM就退出了。
4.2.3 kill -9
最后,如果kill -15還沒有終止JVM,則直接強制退出。
五、啟動腳本DEMO
這里附贈常用JVM的重啟腳本。
用法:./main.sh start|stop|restart|kill|status
查看源碼鏈接:https://github.com/waterystone/shell_test/blob/main/jvm/main.sh
六、參考
- How to gracefully handle the SIGKILL signal in Java:https://stackoverflow.com/questions/2541597/how-to-gracefully-handle-the-sigkill-signal-in-java
- 服務如何優(yōu)雅關閉:https://juejin.cn/post/6844903814181421064