[英]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
,它恰好包含了OutOfMemoryError
和KernelError
类的东西。 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: 问题:
Throwable
? Throwable
不合适? 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: 至于它为什么这样做:
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.