[英]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種不同方法:
“在關閉掛鈎中加入此線程”的作用是有效地將第二種形式降級為(壞的)第三種形式。
有了這個上下文,您現在可以修復 CTMShutdownHook 中損壞的代碼,或者與該鈎子的開發人員交談並向他們解釋允許所有正在運行的線程很好地關閉的聽起來很優雅的想法實際上是糟糕的。
那么作為一個更一般的原則,關閉鈎子應該盡可能少地阻塞並且絕對不應該等待其他線程采取行動。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.