繁体   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