据我所知,Java / JVM中的最佳实践要求您永远不要直接捕获Throwable
,因为它涵盖Error
,其恰好包含OutOfMemoryError
和{{KernelError
1}}。一些参考文献here和here。
但是在Scala标准库中,有一个提取器NonFatal
被广泛推荐(并被广泛使用的流行库如Akka广泛使用)作为catch
中的最终处理程序(如果需要)块。怀疑这个提取器恰好捕获Throwable
并重新抛出它,如果它是致命错误之一。请参阅代码here。
这可以通过一些反汇编的字节码进一步证实:
问题:
Throwable
?NonFatal
的行为会导致严重的问题吗?如果没有,为什么不呢?答案 0 :(得分:9)
请注意,捕捉Throwable
比您可能意识到的更频繁。其中一些案例与Java语言特性紧密结合,可能产生与您所显示的字节代码非常相似的字节代码。
首先,由于字节码级别上没有finally
,因此通过安装Throwable
的异常处理程序来实现它,该异常处理程序将在重新抛出之前执行finally
块的代码如果代码流到达该点,则Throwable
。你现在可以做坏事:
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged, return from finally discards any throwable
return;
}
结果:
没有
try
{
throw new OutOfMemoryError();
}
finally
{
// highly discouraged too, throwing in finally shadows any throwable
throw new RuntimeException("has something happened?");
}
结果:
java.lang.RuntimeException: has something happened?
at Throwables.example2(Throwables.java:45)
at Throwables.main(Throwables.java:14)
但是,当然,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));
}
结果:
holding lock: true
java.lang.OutOfMemoryError has been thrown, holding lock: false
try-with-resource声明更进一步;它可以通过记录close()
操作抛出的后续抑制异常来修改挂起的throwable:
try(AutoCloseable c = () -> { throw new Exception("and closing failed too"); }) {
throw new OutOfMemoryError();
}
结果:
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
此外,当您submit
任务到ExecutorService
时,所有投掷将被捕获并记录在返回的未来中:
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(); }
结果:
caught and wrapped: java.lang.OutOfMemoryError
如果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());
}
结果:
caught and wrapped: java.lang.OutOfMemoryError
但是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();
结果:
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
所以最重要的是,你不应该关注Throwable
是否会被某个地方捕获的技术细节,而是代码的语义。这是用于忽略异常(坏)还是试图继续,尽管已经报告了严重的环境错误(坏)或只是为了执行清理(好)?上面描述的大多数工具都可以用于好的和坏的......
答案 1 :(得分:7)
不推荐捕获throwable,因为你正在进行的任何处理可能会使进程正常崩溃(在出现内存不足错误的情况下),然后以类似僵尸的状态结束,垃圾收集器拼命地尝试释放记忆,冻结一切。因此,在某些情况下,您需要放弃您可能拥有的任何活动交易并尽快崩溃。
然而,如果你正在做的是一个简单的过滤器,捕获和重新投掷Throwable
本身并不是问题。并且NonFatal
正在评估Throwable
以查看它是虚拟机错误,还是线程被中断等,或者换句话说,它正在寻找需要注意的实际错误。
至于为什么这样做:
Throwable
/ Error
NonFatal
也在寻找像InterruptedException
这样的东西,这是人们不尊重的另一种最佳做法那说Scala的NonFatal
并不完美。例如,它也在重新投掷ControlThrowable
,这是一个巨大的错误(以及Scala的非本地回报)。
答案 2 :(得分:1)
如果您在不重新抛出异常的情况下捕获异常,则意味着您可以保证在catch
块完成后程序保持正确状态。
从这个角度来看,抓住一个OutOfMemoryError
没有任何意义,因为如果它已经发生了你不能再相信你JVM并且不能完全修复程序的状态在catch
区块。
在Java中,建议最多抓住Exception
,而不是Throwable
。 NonFatal
构造的作者对哪些例外可以修复而哪些不可修复有不同的看法。
在scala中,我更喜欢捕获NonFatal
而不是Exceptions
,但是在Java中捕获异常仍然有效。
但要为惊喜做好准备:
1) NonFatal
捕获StackOverflowError
(从我的角度来看没有任何意义)
2)case NonFatal(ex) =>
是一个scala代码,必须在异常发生后由JVM执行。而JVM现在已经被打破了。
我在日志中遇到java.lang.NoClassDefFoundError
NonFatal
之类的内容,但真正的原因是StackOverflowError