代码之家  ›  专栏  ›  技术社区  ›  missingfaktor Kevin Wright

非致命性渔获物可以丢弃吗?

  •  9
  • missingfaktor Kevin Wright  · 技术社区  · 7 年前

    据我所知,Java/JVM中的最佳实践要求您永远不要 Throwable 直接,因为它涵盖 Error 这恰好包括以下内容 OutOfMemoryError KernelError .一些参考资料 here here

    然而,在Scala标准库中,有一个提取器 NonFatal 它被广泛推荐(并且被诸如Akka这样的流行库广泛使用)作为 catch 块。正如所怀疑的,这个提取器碰巧捕捉到 可抛出 如果是致命错误之一,请重新显示。查看代码 here

    这可以通过一些反汇编字节码进一步证实:

    Disassembled output

    问题:

    1. 我在第一段中所作的假设正确吗?或者我认为抓不到是不对的 可抛出 ?
    2. 如果这种假设是正确的,那么 非致命的 导致严重问题?如果没有,为什么没有?
    3 回复  |  直到 7 年前
        1
  •  13
  •   Holger    7 年前

    注意,捕捉 Throwable 发生的次数比你可能意识到的要多。其中一些情况与Java语言特性紧密结合,这些特性可能会生成与您所示非常相似的字节码。

    首先,因为没有依赖于 finally 在字节码级别,它是通过为 可抛出 将执行 最后 在重新旋转之前阻止 可抛出 如果代码流达到该点。在这一点上,你可能会做非常糟糕的事情:

    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)
    

    当然,也有合法的使用案例 最后 ,例如进行资源清理。使用类似字节码模式的相关构造是 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() 操作:

    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());
    }
    
    结果:
    捕获并包装: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
    

    所以底线是,您不应该关注技术细节 可抛出 将在某个地方被捕获,但代码的语义。这是用于忽略异常(坏)还是用于在报告了严重环境错误的情况下尝试继续(坏)还是仅用于执行清理(好)?上面描述的大多数工具都可以用于好的和坏的方面

        2
  •  8
  •   Alexandru Nedelcu    7 年前

    不建议捕获throwable,因为您正在执行的任何处理都可能延迟进程正常崩溃(在内存不足错误的情况下),然后最终进入类似僵尸的状态,垃圾收集器拼命尝试释放内存并冻结所有内容。因此,在某些情况下,您需要放弃可能拥有的任何活动事务,并尽快崩溃。

    然而,接球和重新投掷 Throwable 如果您所做的只是一个简单的过滤器,那么这本身并不是一个问题。和 NonFatal 正在评估 可抛出 查看是虚拟机错误,还是线程被中断,等等,或者换句话说,它正在寻找要注意的实际错误。

    至于为什么会这样做:

    • 人们一直在虐待 可抛出 / Error
    • 非致命的 也在寻找类似的东西 InterruptedException ,这是人们不尊重的另一个最佳实践

    也就是说Scala 非致命的 并不完美。例如,它也是重新投掷 ControlThrowable ,这是一个巨大的错误(连同Scala的非本地回报)。

        3
  •  1
  •   simpadjo    7 年前

    如果捕获到异常而不进一步重新引用,则意味着您可以保证程序在 catch 块已完成。

    从这个角度来看,抓住,比如说,一个 OutOfMemoryError 因为如果发生了这种情况,您就不能再信任JVM,也不能很好地修复程序的状态 接住

    在Java中,建议最多捕获 Exception Throwable .作者 NonFatal 对于哪些异常是可修复的,哪些是不可修复的,Construction有一点不同的看法。

    在scala我更喜欢 非致命的 s而不是 Exceptions 但在Java中捕获异常仍然有效。 但要为惊喜做好准备:

    (1) 非致命的 捕获 StackOverflowError (在我看来这毫无意义)

    (2) case NonFatal(ex) => 是一个scala代码,必须在异常发生后由JVM执行。此时JVM可能已经崩溃了。 我曾经面对过 java.lang.NoClassDefFoundError 对于 非致命的 但真正的原因是 堆栈溢出错误