简体   繁体   English

UncaughtExceptionHandler 和 System.exit()

[英]UncaughtExceptionHandler and System.exit()

I've written a custom UncaughtExceptionHandler that should print the exception to the console and shut down the application with a custom exit code.我编写了一个自定义 UncaughtExceptionHandler ,它应该将异常打印到控制台并使用自定义退出代码关闭应用程序。

The class looks like this:这个类看起来像这样:

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);
    }
}

I set the UncaughtExceptionHandler in my Main.class like this:我在 Main.class 中设置 UncaughtExceptionHandler 如下:

Thread.setDefaultUncaughtExceptionHandler(new FatalUncaughtExceptionHandler());

Then I generate and start 4 threads.然后我生成并启动 4 个线程。

In one of the running threads I purposely generate a NumberFormatException using Integer.valueOf("Test") in order to test my Handler.在其中一个正在运行的线程中,我故意使用Integer.valueOf("Test")生成NumberFormatException以测试我的处理程序。 This works fine;这很好用; Here's the output:这是输出:

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)

Now I have a problem.现在我有一个问题。 For some reason the thread in which the exception was thrown is not being shutdown by the System.exit() command.由于某种原因,引发异常的线程没有被 System.exit() 命令关闭。 Apparently my ShutdownHook has a lock on it.显然我的 ShutdownHook 锁定了它。 (As seen in the output of jvisualvm): (如 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

Even IntelliJ tells me that the System.exit command will fail.甚至 IntelliJ 也告诉我 System.exit 命令会失败。 It displays a little badge next to it saying "Method will fail" when debugging my UncaughtExceptionHandler.在调试我的 UncaughtExceptionHandler 时,它旁边会显示一个小徽章,上面写着“方法将失败”。


This leads me to my question:这引出了我的问题:

Is it not allowed to call System.exit() from an UncaughtExceptionHandler?是否不允许从 UncaughtExceptionHandler 调用 System.exit()?

Is the shutdown hook initiated twice in my case?在我的情况下,关闭挂钩是否启动了两次?

What could be the reason for the lock on the shutdown hook?关闭挂钩上的锁定可能是什么原因?

See that com.csg.gfms stuff in the trace?看到跟踪中的com.csg.gfms内容了吗?

It's not java;它不是java; it's you.是你。 That's your code that's blocking in another shutdown hook;那是您的代码在另一个关闭挂钩中阻塞; one that is calling Thread.join .一个调用Thread.join

Generally when running into such weirdness, if it is at all possible to make a stand-alone super simple test case, then you should do so.一般遇到这种怪事时,如果有可能做一个独立的超级简单的测试用例,那么你应该这样做。 I have done this for you:我已经为你做到了:

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?");
        }
}

When I run this, I get an arbitrary number of EXITING prints (2 to 3, it's dependent on how many cores are simultaneously working on these threads), and then the VM hard-exits.当我运行它时,我得到任意数量的EXITING打印(2 到 3,这取决于有多少内核同时在这些线程上工作),然后 VM 硬退出。 Still alive? is never printed, no locking occurs, the VM actually exits.从不打印,不发生锁定,VM 实际退出。

Thus proving that calling System.exit() from within the uncaught exception handler is not an issue.因此证明从未捕获的异常处理程序中调用System.exit()不是问题。

The shutdown hook is not invoked twice;关闭钩子不会被调用两次; the shutdown hook is invoked due to you invoking System.exit , not because we got to the uncaught exception handler.由于您调用System.exit ,而不是因为我们获得了未捕获的异常处理程序,因此调用了关闭挂钩。 But, if you're worried about this, hey, it's your app, print something in your shutdown hooks to be sure.但是,如果您对此感到担心,嘿,这是您的应用程序,请在关闭挂钩中打印一些内容以确保。

The lock issue is not on the shutdown hook.锁的问题是不是关闭挂钩。 You can register any amount of shutdown hooks.您可以注册任意数量的关闭挂钩。 It's in a shutdown hook.这是一个在关闭挂钩。 Specifically: somebody registered an instance of com.csg.gfms.gms.ctmgate.runnable.CTMShutdownHook , that code is joining some thread, and that thread is not shutting down, thus that hook never exits, thus System.exit is not exiting the VM.具体来说:有人注册了一个com.csg.gfms.gms.ctmgate.runnable.CTMShutdownHook的实例,该代码正在加入某个线程,并且该线程没有关闭,因此该钩子永远不会退出,因此 System.exit 没有退出虚拟机。 The solution is to fix CTMShutdownHook, which is broken .解决方案是修复已损坏的 CTMShutdownHook

Joining a thread in a shutdown hook is... well, I'll just say it bluntly: Stupid.在关闭钩子中加入一个线程是......好吧,我直截了当地说:愚蠢。 I don't quite know what this is trying to accomplish, but the only thing I can think of is forced adherence to a bad standard.我不太清楚这是要达到什么目的,但我唯一能想到的就是被迫遵守一个糟糕的标准。 Therefore, I can foresee that you, or the author of CTMShutdownHook, first needs some introspective on how to deal with JVM shutdowns, so that they understand that the idea underlying their implementation is fundamentally misguided and needs to be rethought.因此,我可以预见您,或者 CTMShutdownHook 的作者,首先需要对如何处理 JVM 关闭进行一些反省,以便他们了解其实现背后的想法从根本上被误导,需要重新思考。

I will do that here.我会在这里做。

There is this mindset that to 'properly' shut down a VM, one should never invoke System.exit, one should instead carefully tell all running threads to stop, and one should carefully manage the daemon flag on all threads, so that the VM will end up shutting down on its own volition because all still alive threads have the daemon flag set.有这样一种心态,要“正确”关闭 VM,永远不要调用 System.exit,而是应该小心地告诉所有正在运行的线程停止,并且应该仔细管理所有线程上的守护程序标志,以便 VM最终会自行关闭,因为所有仍然活动的线程都设置了守护进程标志。 The argument being that this gives each thread the chance to 'shut down nicely'.争论是这让每个线程都有机会“很好地关闭”。

This is bad code style.这是糟糕的代码风格。

Your app will just shut down if someone hits CTRL+C or otherwise asks the VM to exit, this will not result in a nice 'ask all threads to clean up and stop' process.如果有人按下 CTRL+C 或以其他方式要求 VM 退出,您的应用程序将关闭,这不会导致“要求所有线程清理并停止”进程。 In fact, your app gets zero opportunity to clean up anything if someone trips over a powercable, the computer hard-crashes, or someone terminates the app.事实上,如果有人被电源线绊倒、计算机严重崩溃或有人终止应用程序,您的应用程序将没有机会清理任何内容。

This leads to the following rules:这导致以下规则:

  • Any code that is written so that it breaks if not shut down nicely (eg you keep some state in memory, and upon being asked to quit, you save this state to disk; it is quite a serious bug if this state is just forgotten) is bad code .任何编写的代码,如果没有很好地关闭就会中断(例如,您将某些状态保留在内存中,并在被要求退出时,将此状态保存到磁盘;如果只是忘记了此状态,则这是一个非常严重的错误)是坏代码 It is always possible to write code so that recovery is possible.始终可以编写代码以便进行恢复。 Even extreme cases, such as filesystems, can (these days) handle just pulling the cord using eg journalling technology.即使是极端情况,例如文件系统,(现在)也可以使用例如日志技术来处理。
  • If you want to at least 'be nice' and try to save state or otherwise cleanup, do not wait for someone to tell your thread to 'exit nicely'.如果您想至少“保持良好”并尝试保存状态或以其他方式进行清理,请不要等待有人告诉您的线程“良好退出”。 Just register a shutdown handler which does the cleanup, and assume your main thread loop will just straight up abort at some arbitrary point without any further notification.只需注册一个执行清理的关闭处理程序,并假设您的主线程循环将在某个任意点直接中止,而无需任何进一步通知。 This is not actually hard to write.这实际上并不难写。
  • Said differently: Don't ever assume your thread will be told to clean up after itself.换种说法:永远不要假设您的线程会被告知要自行清理。 Assume that usually any registered shutdownhandlers are invoked, but don't rely on them entirely, as in rare scenarios (power pulse, kill -9 , VM core crash, memory issues, someone runs this in an IDE and just kills it, which is usually a hard-kill, the list is long) those don't run either.假设通常调用任何已注册的关闭处理程序,但不要完全依赖它们,因为在极少数情况下(电源脉冲、 kill -9 、VM 内核崩溃、内存问题,有人在 IDE 中运行它并直接杀死它,这是通常是硬杀,列表很长)那些也不会运行。

By adding a shutdownhook that 'joins' a thread (joining = pause this thread until that thread exits), you've created a very silly scenario where of the 3 different ways to shut an app down:通过添加一个“加入”线程的shutdownhook(加入=暂停该线程直到该线程退出),您创建了一个非常愚蠢的场景,其中关闭应用程序的3种不同方法:

  • Someone trips over a powercable or kill -9s your app: Everything dies on the spot, no cleanup possible.有人被电源线绊倒或杀死 -9s 你的应用程序:一切都当场死亡,无法清理。
  • CTRL+C is hit or someone calls System.exit or normal SIGKILLs your app: Everything dies on the spot, but all shutdown hooks are invoked.按 CTRL+C 或有人调用 System.exit 或正常 SIGKILLs 您的应用程序:一切都当场死亡,但所有关闭挂钩都被调用。
  • (Misguided) Within the app some process starts trying to get all non-daemon threads to return, and they will presumably be doing their cleanup internally. (误导)在应用程序中,一些进程开始尝试让所有非守护线程返回,并且它们可能会在内部进行清理。

What 'join this thread in a shutdown hook' does is effectively downgrade that second form to the (bad) third form. “在关闭挂钩中加入此线程”的作用是有效地将第二种形式降级为(坏的)第三种形式。

With that context, you can now fix the broken code in CTMShutdownHook, or talk to the developer of that hook and explain to them that the elegant-sounding idea of allowing all running threads to shut down nicely is in fact bad.有了这个上下文,您现在可以修复 CTMShutdownHook 中损坏的代码,或者与该钩子的开发人员交谈并向他们解释允许所有正在运行的线程很好地关闭的听起来很优雅的想法实际上是糟糕的。

Then as a more general point of principle, shutdown hooks should block as little as possible and should definitely not wait for other threads to act.那么作为一个更一般的原则,关闭钩子应该尽可能少地阻塞并且绝对不应该等待其他线程采取行动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM