[英]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.