简体   繁体   English

NonFatal能抓住Throwable吗?

[英]Is it okay that NonFatal catches Throwable?

As I understand it, the best practice in Java/JVM dictate that you should never catch Throwable directly, since it covers Error which happens to encompass things like OutOfMemoryError and KernelError . 据我所知,Java / JVM中的最佳实践要求你永远不要直接捕获Throwable ,因为它涵盖了Error ,它恰好包含了OutOfMemoryErrorKernelError类的东西。 Some references here and here . 这里这里有一些参考。

However in Scala standard library, there is an extractor NonFatal that is widely recommended (and widely used by popular libraries such as Akka) as a final handler (if you need one) in your catch blocks. 但是在Scala标准库中,有一个提取器NonFatal被广泛推荐(并被广泛使用的流行库如Akka)作为catch块中的最终处理程序(如果需要)。 This extractor, as suspected, happens to catch Throwable and rethrow it if it is one of the fatal errors. 怀疑这个提取器碰巧捕获Throwable并重新抛出它,如果它是致命错误之一。 See the code here . 请参阅此处的代码。

This can be further confirmed by some disassembled bytecode: 这可以通过一些反汇编的字节码进一步证实:

拆卸输出

Questions: 问题:

  1. Is the assumption I made in my first paragraph correct? 我在第一段中做出的假设是否正确? Or am I incorrect in assuming it's not okay to catch Throwable ? 或者我错误地认为抓住Throwable不合适?
  2. If that assumption is correct, could the behaviour of NonFatal lead to serious problems? 如果这个假设是正确的,那么NonFatal的行为NonFatal导致严重的问题吗? If not, why not? 如果没有,为什么不呢?

Note that catching Throwable happens more often than you might be aware of. 请注意,捕获Throwable比您可能意识到的更频繁。 Some of these cases are tightly coupled with Java language features which may produce byte code very similar to the one you have shown. 其中一些案例与Java语言功能紧密结合,可能会产生与您所显示的字节代码非常相似的字节代码。

First, since there is no pendent to finally on the bytecode level, it gets implemented by installing an exception handler for Throwable which will execute the code of the finally block before rethrowing the Throwable if the code flow reaches that point. 首先,由于finally没有对字节码级别的依赖,它通过为Throwable安装异常处理程序来实现,如果代码流到达该点,它将在重新抛出Throwable之前执行finally块的代码。 You could do really bad things at this point: 你现在可以做很糟糕的事情:

try
{
    throw new OutOfMemoryError();
}
finally
{
    // highly discouraged, return from finally discards any throwable
    return;
}
Result: 结果:

Nothing 没有

 try { throw new OutOfMemoryError(); } finally { // highly discouraged too, throwing in finally shadows any throwable throw new RuntimeException("has something happened?"); } 
Result: 结果:
 java.lang.RuntimeException: has something happened? at Throwables.example2(Throwables.java:45) at Throwables.main(Throwables.java:14) 

But of course, there are legitimate use cases for finally , like doing resource cleanup. 但是,当然, finally有合法的用例,比如进行资源清理。 A related construct using a similar byte code pattern is synchronized , which will release the object monitor before re-throwing: 使用类似字节代码模式的相关构造被synchronized ,这将在重新抛出之前释放对象监视器:

java.lang.OutOfMemoryError
    at Throwables.example4(Throwables.java:64)
    at Throwables.main(Throwables.java:18)
    Suppressed: java.lang.Exception: and closing failed too
        at Throwables.lambda$example4$0(Throwables.java:63)
        at Throwables.example4(Throwables.java:65)
        ... 1 more
Result: 结果:
caught and wrapped: java.lang.OutOfMemoryError

The try-with-resource statement takes this even further; try-with-resource语句更进一步; it might modify the pending throwable by recording subsequent suppressed exceptions thrown by the close() operation(s): 它可以通过记录close()操作抛出的后续抑制异常来修改挂起的throwable:

FutureTask<Object> f = new FutureTask<>(() -> { throw new OutOfMemoryError(); });
f.run(); // see, it has been caught
try {
    f.get();
}
catch(ExecutionException ex) {
    System.out.println("caught and wrapped: "+ex.getCause());
}
Result: 结果:
// using Runnable::run as Executor means we're executing it directly in our thread
CompletableFuture<Void> cf = CompletableFuture.runAsync(
    () -> { throw new OutOfMemoryError(); }, Runnable::run);
System.out.println("if we reach this point, the throwable must have been caught");
cf.join();

Further, when you submit a task to an ExecutorService , all throwables will be caught and recorded in the returned future: 此外,当您submit ExecutorService submit任务时,所有throwable将被捕获并记录在返回的未来中:

 ExecutorService es = Executors.newSingleThreadExecutor(); Future<Object> f = es.submit(() -> { throw new OutOfMemoryError(); }); try { f.get(); } catch(ExecutionException ex) { System.out.println("caught and wrapped: "+ex.getCause()); } finally { es.shutdown(); } 
Result: 结果:
 caught and wrapped: java.lang.OutOfMemoryError 

In the case of the JRE provided executor services, the responsibility lies at the FutureTask which is the default RunnableFuture used internally. 在JRE提供的执行程序服务的情况下,责任在于FutureTask ,它是内部使用的默认RunnableFuture We can demonstrate the behavior directly: 我们可以直接演示这种行为:

 FutureTask<Object> f = new FutureTask<>(() -> { throw new OutOfMemoryError(); }); f.run(); // see, it has been caught try { f.get(); } catch(ExecutionException ex) { System.out.println("caught and wrapped: "+ex.getCause()); } 
Result: 结果:
 caught and wrapped: java.lang.OutOfMemoryError 

But CompletableFuture exhibits a similar behavior of catching all throwables. CompletableFuture表现出类似捕捉所有掷骰子的行为。

 // using Runnable::run as Executor means we're executing it directly in our thread CompletableFuture<Void> cf = CompletableFuture.runAsync( () -> { throw new OutOfMemoryError(); }, Runnable::run); System.out.println("if we reach this point, the throwable must have been caught"); cf.join(); 
Result: 结果:
 if we reach this point, the throwable must have been caught java.util.concurrent.CompletionException: java.lang.OutOfMemoryError at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:314) at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:319) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1739) at java.base/java.util.concurrent.CompletableFuture.asyncRunStage(CompletableFuture.java:1750) at java.base/java.util.concurrent.CompletableFuture.runAsync(CompletableFuture.java:1959) at Throwables.example7(Throwables.java:90) at Throwables.main(Throwables.java:24) Caused by: java.lang.OutOfMemoryError at Throwables.lambda$example7$3(Throwables.java:91) at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736) ... 4 more 

So the bottom line is, you should not focus on the technical detail of whether Throwable will be caught somewhere, but the semantic of the code. 所以最重要的是,你不应该关注Throwable是否会被某个地方捕获的技术细节,而是代码的语义。 Is this used for ignoring exceptions (bad) or for trying to continue despite serious environmental errors have been reported (bad) or just for performing cleanup (good)? 这是用于忽略异常(坏)还是试图继续,尽管已经报告了严重的环境错误(坏)或只是为了执行清理(好)? Most of the tools described above can be used for the good and the bad… 上面描述的大多数工具都可以用于好的和坏的......

Catching throwable is not recommended because whatever processing that you're doing could delay the process rightfully crashing (in the event of an out of memory error) and then ending up in a zombie like state, with the garbage collector desperately trying to free up memory and freezing everything. 不推荐捕获throwable,因为你正在进行的任何处理可能会使进程正常崩溃(在内存不足的情况下出现错误),然后以僵尸状态结束,垃圾收集器拼命地试图释放内存并冻结一切。 So there are instances in which you need to give up on any active transactions you may have and crash as soon as possible. 因此,有些情况下您需要放弃您可能拥有的任何活动交易并尽快崩溃。

However catching and re-throwing Throwable isn't a problem per se if what you're doing is a simple filter. 然而捕捉和重新投掷Throwable本身并不是问题,如果你正在做的是一个简单的过滤器。 And NonFatal is evaluating that Throwable to see if it's a virtual machine error, or the thread being interrupted, etc, or in other words it's looking for the actual errors to watch out for. NonFatal正在评估Throwable是否是虚拟机错误,或线程被中断等,或者换句话说,它正在寻找需要注意的实际错误。

As for why it is doing that: 至于它为什么这样做:

  • people have been abusing Throwable / Error 人们一直在滥用Throwable / Error
  • NonFatal is also looking for things like InterruptedException , which is another best practice that people aren't respecting NonFatal也在寻找像InterruptedException这样的东西,这是人们不尊重的另一种最佳做法

That said Scala's NonFatal isn't perfect. 那说Scala的NonFatal并不完美。 For example it is also re-throwing ControlThrowable , which is a huge mistake (along with Scala's non-local returns). 例如,它也重新抛出ControlThrowable ,这是一个巨大的错误(以及Scala的非本地回报)。

If you catch an exception without rethrowing it further it means that you can guarantee that the program stays in correct state after the catch block is finished. 如果您在不重新抛出异常的情况下捕获异常,则意味着您可以保证在catch块完成后程序保持正确状态。

From this point of view it doesn't make any sense to catch, say, an OutOfMemoryError because if it has happened you can't trust you JVM anymore and can't soundly repair the state of your program in catch block. 从这个角度来看,捕获OutOfMemoryError是没有任何意义的,因为如果它发生了,你就不能再信任JVM了,并且无法在catch块中正确地修复程序的状态。

In Java it's recommended to catch at most Exception , not Throwable . 在Java中,建议最多捕获Exception ,而不是Throwable Authors of NonFatal construct have a bit different opinion about which exceptions are repairable and which are not. NonFatal构造的作者对哪些例外是可修复的而哪些不可修复有不同的看法。

In scala I prefer to catch NonFatal s instead of Exceptions but catching Exceptions as in Java is still valid. 在scala中,我更喜欢捕获NonFatal而不是Exceptions但是在Java中捕获异常仍然有效。 But be prepared for surprises: 但要为惊喜做好准备:

1) NonFatal catches StackOverflowError (it makes no sense from my point of view) 1) NonFatal捕获StackOverflowError (从我的角度来看没有任何意义)

2) case NonFatal(ex) => is a scala code that has to be executed by JVM after the exception has already occurred. 2) case NonFatal(ex) =>是一个scala代码,必须在异常发生后由JVM执行。 And the JVM can be already broken at this moment. 而JVM现在已经被打破了。 I faced once something like java.lang.NoClassDefFoundError for NonFatal in my logs but the real reason was StackOverflowError 在我的日志中,我曾经遇到类似于NonFatal java.lang.NoClassDefFoundError ,但真正的原因是StackOverflowError

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

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