聊聊如何優雅的關閉服務?
大家好,我是指北君。
通常,啟動一個服務是很容易的。然而,有時我們需要有一個計劃來優雅地關閉一個服務。
在本教程中,我們將看一下 JVM 應用程序終止的不同方式。然后,我們將使用 Java APIs 來管理 JVM 關閉鉤子。
關閉 JVM
JVM 可以通過兩種不同的方式被關閉。
- 一種受控的方式
- 一種非受控的方式
一個受控的進程在以下兩種情況下關閉 JVM。
- 最后一個非 daemon 線程終止。例如,當主線程退出時,JVM 開始其關閉進程
- 從操作系統發送一個中斷信號。例如,通過按 Ctrl + C 或注銷操作系統
- 從 Java 代碼中調用 System.exit()
雖然我們都在努力爭取優雅的關閉,但有時 JVM 可能會以突然和意外的方式關閉。JVM 會在以下情況下突然關閉。
- 從操作系統發送一個 kill 信號。例如,通過發出 kill -9 的信號
- 從 Java 代碼中調用 Runtime.getRuntime().halt() 。
- 主機操作系統意外關閉,例如,在電源故障或操作系統崩潰的情況下
shutdown hook
JVM 允許在完成關機之前運行注冊函數。這些函數通常是釋放資源或其他類似的內部管理任務的好地方。在 JVM 的術語中,這些函數被稱為關閉鉤子。
關閉鉤子基本上是初始化但未啟動的線程。當JVM開始其關閉過程時,它將以一個未指定的順序啟動所有注冊的鉤子。在運行完所有鉤子后,JVM 將停止運行。
添加鉤子
為了添加一個關閉鉤子,我們可以使用 Runtime.getRuntime().addShutdownHook() 方法。
Thread printingHook = new Thread(() -> System.out.println("我要關閉了"));
Runtime.getRuntime().addShutdownHook(printingHook);
在這里,我們只是在JVM自行關閉之前向標準輸出端打印一些東西。如果我們像下面這樣關閉JVM。
System.exit(123);
我要關閉了
然后我們會看到,鉤子實際上是將消息打印到標準輸出。
JVM負責啟動鉤子線程。因此,如果給定的鉤子已經被啟動了,Java將拋出一個異常。
Thread longRunningHook = new Thread(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException ignored) {}
});
longRunningHook.start();
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(longRunningHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("鉤子正在運行");
很明顯,我們也不能多次注冊一個鉤子。
Thread unfortunateHook = new Thread(() -> {});
Runtime.getRuntime().addShutdownHook(unfortunateHook);
assertThatThrownBy(() -> Runtime.getRuntime().addShutdownHook(unfortunateHook))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("鉤子已經注冊");
刪除鉤子
Java 提供了一個孿生的移除方法,以便在注冊一個特定的關閉鉤子后將其移除。
Thread willNotRun = new Thread(() -> System.out.println("鉤子不會運行的"));
Runtime.getRuntime().addShutdownHook(willNotRun);
assertThat(Runtime.getRuntime().removeShutdownHook(willNotRun)).isTrue();
當關閉鉤子被成功刪除時,removeShutdownHook() 方法返回true。
注意事項
JVM 只在正常終止的情況下運行關閉鉤子。因此,當外部力量突然殺死JVM進程時,JVM將沒有機會執行關閉鉤子。此外,從Java代碼中停止JVM也會產生同樣的效果。
Thread haltedHook = new Thread(() -> System.out.println("強行終止"));
Runtime.getRuntime().addShutdownHook(haltedHook);
Runtime.getRuntime().halt(123);
halt 方法強行終止了當前運行的JVM。因此,注冊的關閉鉤子不會有機會執行。
總結
在本教程中,我們研究了 JVM 應用程序可能終止的不同方式。然后,我們使用一些運行時API來注冊和取消注冊關閉鉤子。






























