首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java Shutdown Hook 詳解:JVM 關閉前的優雅清理機制

     編輯:Java 程式設計入門

什麼是 Shutdown Hook?

Shutdown Hook(關閉鉤子)是已初始化但尚未啟動的執行緒(Thread),向 Runtime 註冊後,當 JVM 即將正常關閉時,會並發啟動所有掛鉤執行緒執行清理工作。適用於:釋放資源、持久化狀態、關閉連線池、記錄審計日誌等「短暫、必要」的收尾任務。

註冊與執行流程

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    // 清理邏輯
    logger.info("Shutdown hook executing...");
    cleanupResources();
}));

JVM 正常關閉觸發條件(任一即可):

  1. 呼叫 System.exit(int)
  2. 使用者在終端機按 Ctrl+C(SIGINT)
  3. 最後一個非守護執行緒結束
  4. 作業系統使用者登出或系統關機(SIGTERM)

完整範例:帶延遲模擬的清理任務

public class ShutdownHookDemo implements Runnable {

    public ShutdownHookDemo() {
        Runtime.getRuntime().addShutdownHook(new Thread(this));
        System.out.println(">>> Shutdown hook registered");
    }

    @Override
    public void run() {
        System.out.println("\n>>> Executing shutdown cleanup...");
        cleanUp();
        System.out.println(">>> Cleanup finished.");
    }

    private void cleanUp() {
        for (int i = 0; i < 7; i++) {
            System.out.println("Cleanup step " + i);
            try { Thread.sleep(200); } catch (InterruptedException ignored) {}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ShutdownHookDemo();
        System.out.println(">>> Sleeping 5 sec, try Ctrl+C now...");
        Thread.sleep(5000);
        System.out.println(">>> Main thread exiting normally.");
    }
}

關鍵特性與注意事項

1. 執行順序不保證

多個掛鉤 並發啟動,無順序保證。若有依賴關係,需自行協調(如單一掛鉤內按序呼叫、使用 CountDownLatch 等)。

2. 執行時間應盡量短

JVM 關閉過程中,作業系統可能強制終止進程(如 Windows 服務關閉超時、Kubernetes terminationGracePeriodSeconds)。長時間任務風險極高,建議 < 5 秒 內完成。

3. 不會在以下情況執行

  • Runtime.halt() 強制終止(不運行關閉鉤、不執行 finally、不執行 finalizer)
  • JVM Crash(Segmentation Fault、OOM Killer、硬體故障、kill -9)
  • 作業系統強制關機超時

4. 資源佔用極低

掛鉤執行緒在 start() 前不佔用系統執行緒資源(大多數 JVM 實作)。註冊成百上千個掛鉤僅佔用少量堆記憶體儲存 Thread 物件,無效能顧慮。

5. 異常處理

掛鉤執行緒拋出未捕獲異常會印堆疊追蹤但 不會阻止其他掛鉤執行。建議在 run() 內部 try-catch 吞噬異常,避免汙染日誌。

生產環境最佳實務

  1. 單一責任:每個掛鉤只做一件事(如關閉連線池、刷新緩存)。
  2. 冪等性:清理邏輯可重複呼叫不報錯(如 close() 多次安全)。
  3. 超時保護:使用 ExecutorService + Future.get(timeout) 包裝清理任務,防止卡死。
  4. 避免依賴應用上下文:掛鉤執行時,Spring 容器可能已銷毀、靜態欄位可能已清空,僅依賴 JDK 基礎類庫。
  5. 記錄關鍵日誌:啟動/完成/失敗均記錄,便於事後排查。

Spring 整合範例

@Component
public class GracefulShutdown implements DisposableBean, ApplicationListener {

    private final ThreadPoolTaskExecutor taskExecutor;

    public GracefulShutdown(ThreadPoolTaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
        // 註冊 JVM 掛鉤作為雙重保險
        Runtime.getRuntime().addShutdownHook(new Thread(this::doShutdown));
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        doShutdown();  // Spring 關閉時觸發
    }

    @Override
    public void destroy() throws Exception {
        doShutdown();  // Bean 銷毀時觸發
    }

    private void doShutdown() {
        System.out.println(">>> Graceful shutdown initiated...");
        taskExecutor.shutdown();
        try {
            if (!taskExecutor.getThreadPoolExecutor().awaitTermination(10, TimeUnit.SECONDS)) {
                taskExecutor.getThreadPoolExecutor().shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            taskExecutor.getThreadPoolExecutor().shutdownNow();
        }
        System.out.println(">>> Thread pool terminated.");
    }
}

總結

Shutdown Hook 是 JVM 提供的「最後防線」,讓應用在正常關閉時有機會體面收尾。但它不保證一定執行,核心資料持久化、分散式協調等關鍵邏輯,必須在業務層面設計冪等、可重試、可補償機制,不能寄望於關閉鉤。正確心態:有則優雅,無則不影響正確性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved