mis*_*tor 8 java jvm scala exception-handling akka
据我所知,Java/JVM中的最佳实践要求你永远不要Throwable直接捕获,因为它涵盖的Error内容恰好包含OutOfMemoryError和KernelError.这里和这里有一些参考.
但是在Scala标准库中,有一个提取器NonFatal被广泛推荐(并被广泛使用的流行库如Akka)作为块中的最终处理程序(如果需要)catch.如果怀疑这个提取器恰好是捕获Throwable并重新抛出它,如果它是致命错误之一.请参阅此处的代码.
这可以通过一些反汇编的字节码进一步证实:
问题:
Throwable?NonFatal导致严重的问题吗?如果没有,为什么不呢?请注意,捕获Throwable比您可能意识到的更频繁.其中一些案例与Java语言功能紧密结合,可能会产生与您所显示的字节代码非常相似的字节代码.
首先,由于finally字节码级别没有悬念,因此通过安装异常处理程序来实现Throwable它finally,Throwable如果代码流到达该点,则在重新抛出之前执行块的代码.你现在可以做很糟糕的事情:
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged, return from finally discards any throwable
return;
}
Run Code Online (Sandbox Code Playgroud)
结果:
没有
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged too, throwing in finally shadows any throwable
throw new RuntimeException("has something happened?");
}
Run Code Online (Sandbox Code Playgroud)
结果:
java.lang.RuntimeException: has something happened?
at Throwables.example2(Throwables.java:45)
at Throwables.main(Throwables.java:14)
Run Code Online (Sandbox Code Playgroud)
但是,当然,有合理的用例finally,比如进行资源清理.使用类似字节代码模式的相关构造synchronized,将在重新抛出之前释放对象监视器:
Object lock = new Object();
try
{
synchronized(lock) {
System.out.println("holding lock: "+Thread.holdsLock(lock));
throw new OutOfMemoryError();
}
}
catch(Throwable t) // just for demonstration
{
System.out.println(t+" has been thrown, holding lock: "+Thread.holdsLock(lock));
}
Run Code Online (Sandbox Code Playgroud)
结果:
holding lock: true
java.lang.OutOfMemoryError has been thrown, holding lock: false
Run Code Online (Sandbox Code Playgroud)
在试穿与资源语句采用这种更进一步; 它可以通过记录close()操作抛出的后续抑制异常来修改挂起的throwable :
try(AutoCloseable c = () -> { throw new Exception("and closing failed too"); }) {
throw new OutOfMemoryError();
}
Run Code Online (Sandbox Code Playgroud)
结果:
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
Run Code Online (Sandbox Code Playgroud)
此外,当您submit执行任务时ExecutorService,所有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(); }
Run Code Online (Sandbox Code Playgroud)
结果:
caught and wrapped: java.lang.OutOfMemoryError
Run Code Online (Sandbox Code Playgroud)
在JRE提供的执行程序服务的情况下,责任在于内部使用FutureTask的默认值RunnableFuture.我们可以直接演示这种行为:
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());
}
Run Code Online (Sandbox Code Playgroud)
结果:
caught and wrapped: java.lang.OutOfMemoryError
Run Code Online (Sandbox Code Playgroud)
但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();
Run Code Online (Sandbox Code Playgroud)
结果:
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
Run Code Online (Sandbox Code Playgroud)
所以最重要的是,你不应该关注是否Throwable会被某个地方捕获的技术细节,而是代码的语义.这是用于忽略异常(坏)还是试图继续,尽管已经报告了严重的环境错误(坏)或只是为了执行清理(好)?上面描述的大多数工具都可以用于好的和坏的......
不推荐捕获throwable,因为你正在进行的任何处理可能会使进程正常崩溃(在内存不足的情况下出现错误),然后以类似僵尸的状态结束,垃圾收集器拼命地试图释放内存并冻结一切.因此,有些情况下您需要放弃您可能拥有的任何活动交易并尽快崩溃.
然而,Throwable如果你正在做的是一个简单的过滤器,捕获和重新投掷本身不是问题.并且NonFatal正在评估Throwable它是否是虚拟机错误,或线程被中断等,或者换句话说,它正在寻找需要注意的实际错误.
至于它为什么这样做:
Throwable/ErrorNonFatal也在寻找类似的东西InterruptedException,这是人们不尊重的另一种最佳实践那说Scala NonFatal并不完美.例如,它也是重新投掷ControlThrowable,这是一个巨大的错误(与Scala的非本地回报一起).