簡體   English   中英

UncaughtExceptionHandler 和 System.exit()

[英]UncaughtExceptionHandler and System.exit()

我編寫了一個自定義 UncaughtExceptionHandler ,它應該將異常打印到控制台並使用自定義退出代碼關閉應用程序。

這個類看起來像這樣:

public class FatalUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(final Thread t, final Throwable e) {
        System.out.println("Handled exception in " + t.getName() + ":");

        e.printStackTrace();

        System.exit(ExitCodes.UNKNOWN_EXCEPTION);
    }
}

我在 Main.class 中設置 UncaughtExceptionHandler 如下:

Thread.setDefaultUncaughtExceptionHandler(new FatalUncaughtExceptionHandler());

然后我生成並啟動 4 個線程。

在其中一個正在運行的線程中,我故意使用Integer.valueOf("Test")生成NumberFormatException以測試我的處理程序。 這很好用; 這是輸出:

Handled exception in WatchdogThread:
java.lang.NumberFormatException: For input string: "Test"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at com.csg.gfms.gms.ctmgate.runnable.WatchdogThread.run(WatchdogThread.java:43)

現在我有一個問題。 由於某種原因,引發異常的線程沒有被 System.exit() 命令關閉。 顯然我的 ShutdownHook 鎖定了它。 (如 jvisualvm 的輸出所示):


"WatchdogThread" #38 prio=5 os_prio=0 tid=0x000000001efa3800 nid=0xd40 in Object.wait() [0x0000000021a5e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076e30a7c0> (a com.csg.gfms.gms.ctmgate.runnable.CTMShutdownHook)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x000000076e30a7c0> (a com.csg.gfms.gms.ctmgate.runnable.CTMShutdownHook)
        at java.lang.Thread.join(Thread.java:1326)
        at java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:107)
        at java.lang.ApplicationShutdownHooks$1.run(ApplicationShutdownHooks.java:46)
        at java.lang.Shutdown.runHooks(Shutdown.java:123)
        at java.lang.Shutdown.sequence(Shutdown.java:167)
        at java.lang.Shutdown.exit(Shutdown.java:212)
        - locked <0x00000006c9605b00> (a java.lang.Class for java.lang.Shutdown)
        at java.lang.Runtime.exit(Runtime.java:109)
        at java.lang.System.exit(System.java:971)
        at com.csg.gfms.gms.ctmgate.handlers.FatalUncaughtExceptionHandler.uncaughtException(FatalUncaughtExceptionHandler.java:13)
        at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1057)
        at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:1052)
        at java.lang.Thread.dispatchUncaughtException(Thread.java:1959

甚至 IntelliJ 也告訴我 System.exit 命令會失敗。 在調試我的 UncaughtExceptionHandler 時,它旁邊會顯示一個小徽章,上面寫着“方法將失敗”。


這引出了我的問題:

是否不允許從 UncaughtExceptionHandler 調用 System.exit()?

在我的情況下,關閉掛鈎是否啟動了兩次?

關閉掛鈎上的鎖定可能是什么原因?

看到跟蹤中的com.csg.gfms內容了嗎?

它不是java; 是你。 那是您的代碼在另一個關閉掛鈎中阻塞; 一個調用Thread.join

一般遇到這種怪事時,如果有可能做一個獨立的超級簡單的測試用例,那么你應該這樣做。 我已經為你做到了:

class Test {
        public static void main(String[] args) throws Exception {
                Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                        public void uncaughtException(Thread t, Throwable e) {
                                System.out.println("EXITING");
                                System.exit(1);
                        }
                });
                for (int i = 0; i < 10; i++) {
                        Thread t = new Thread(new Runnable() {
                                public void run() {
                                        try {
                                                Thread.sleep(1000L);
                                        } catch (Exception ignore) {}
                                        throw new RuntimeException();
                                }
                        });
                        t.start();
                }
                Thread.sleep(2000L);
                System.out.println("Still alive?");
        }
}

當我運行它時,我得到任意數量的EXITING打印(2 到 3,這取決於有多少內核同時在這些線程上工作),然后 VM 硬退出。 Still alive? 從不打印,不發生鎖定,VM 實際退出。

因此證明從未捕獲的異常處理程序中調用System.exit()不是問題。

關閉鈎子不會被調用兩次; 由於您調用System.exit ,而不是因為我們獲得了未捕獲的異常處理程序,因此調用了關閉掛鈎。 但是,如果您對此感到擔心,嘿,這是您的應用程序,請在關閉掛鈎中打印一些內容以確保。

鎖的問題是不是關閉掛鈎。 您可以注冊任意數量的關閉掛鈎。 這是一個在關閉掛鈎。 具體來說:有人注冊了一個com.csg.gfms.gms.ctmgate.runnable.CTMShutdownHook的實例,該代碼正在加入某個線程,並且該線程沒有關閉,因此該鈎子永遠不會退出,因此 System.exit 沒有退出虛擬機。 解決方案是修復已損壞的 CTMShutdownHook

在關閉鈎子中加入一個線程是......好吧,我直截了當地說:愚蠢。 我不太清楚這是要達到什么目的,但我唯一能想到的就是被迫遵守一個糟糕的標准。 因此,我可以預見您,或者 CTMShutdownHook 的作者,首先需要對如何處理 JVM 關閉進行一些反省,以便他們了解其實現背后的想法從根本上被誤導,需要重新思考。

我會在這里做。

有這樣一種心態,要“正確”關閉 VM,永遠不要調用 System.exit,而是應該小心地告訴所有正在運行的線程停止,並且應該仔細管理所有線程上的守護程序標志,以便 VM最終會自行關閉,因為所有仍然活動的線程都設置了守護進程標志。 爭論是這讓每個線程都有機會“很好地關閉”。

這是糟糕的代碼風格。

如果有人按下 CTRL+C 或以其他方式要求 VM 退出,您的應用程序將關閉,這不會導致“要求所有線程清理並停止”進程。 事實上,如果有人被電源線絆倒、計算機嚴重崩潰或有人終止應用程序,您的應用程序將沒有機會清理任何內容。

這導致以下規則:

  • 任何編寫的代碼,如果沒有很好地關閉就會中斷(例如,您將某些狀態保留在內存中,並在被要求退出時,將此狀態保存到磁盤;如果只是忘記了此狀態,則這是一個非常嚴重的錯誤)是壞代碼 始終可以編寫代碼以便進行恢復。 即使是極端情況,例如文件系統,(現在)也可以使用例如日志技術來處理。
  • 如果您想至少“保持良好”並嘗試保存狀態或以其他方式進行清理,請不要等待有人告訴您的線程“良好退出”。 只需注冊一個執行清理的關閉處理程序,並假設您的主線程循環將在某個任意點直接中止,而無需任何進一步通知。 這實際上並不難寫。
  • 換種說法:永遠不要假設您的線程會被告知要自行清理。 假設通常調用任何已注冊的關閉處理程序,但不要完全依賴它們,因為在極少數情況下(電源脈沖、 kill -9 、VM 內核崩潰、內存問題,有人在 IDE 中運行它並直接殺死它,這是通常是硬殺,列表很長)那些也不會運行。

通過添加一個“加入”線程的shutdownhook(加入=暫停該線程直到該線程退出),您創建了一個非常愚蠢的場景,其中關閉應用程序的3種不同方法:

  • 有人被電源線絆倒或殺死 -9s 你的應用程序:一切都當場死亡,無法清理。
  • 按 CTRL+C 或有人調用 System.exit 或正常 SIGKILLs 您的應用程序:一切都當場死亡,但所有關閉掛鈎都被調用。
  • (誤導)在應用程序中,一些進程開始嘗試讓所有非守護線程返回,並且它們可能會在內部進行清理。

“在關閉掛鈎中加入此線程”的作用是有效地將第二種形式降級為(壞的)第三種形式。

有了這個上下文,您現在可以修復 CTMShutdownHook 中損壞的代碼,或者與該鈎子的開發人員交談並向他們解釋允許所有正在運行的線程很好地關閉的聽起來很優雅的想法實際上是糟糕的。

那么作為一個更一般的原則,關閉鈎子應該盡可能少地阻塞並且絕對不應該等待其他線程采取行動。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM