【问题标题】:Is throwing an exception inside a finally block a performance issue?在 finally 块中抛出异常是性能问题吗?
【发布时间】:2015-10-21 22:05:16
【问题描述】:

在 Rational Application Developer(基于 Eclipse 的 RAD)中,在软件分析器下,我看到代码审查注释(在 Performance =>Memory 部分下)说“避免在 finally 中使用 throw 语句”。

在 finally 块中定义 throw 对性能有何影响?

这里是代码sn-p,我们已经建议改代码来记录异常跟踪,不要抛出异常,

     } finally {
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (final IOException ex) {
                throw ex;
            }
        }
    }

我只是想知道这会如何影响内存和性能?

【问题讨论】:

  • 你到底为什么要在 finally 块中抛出异常? 不要这样做。永远。
  • 如果您有两个对象需要清理,AB,但清理 A 已经引发异常,缺少清理 B 可能会造成内存泄漏。但这只是猜测。在 finally 块中抛出异常不是一个好主意,但内存问题不是我的首要原因。
  • @BoristheSpider 最简单的finally 块,{ stream.close(); } 涉及声明抛出IOException 的方法调用。所以看起来每个人都在这样做。
  • catch (final IOException ex) { throw ex; } -- 这段代码毫无意义。删除它,您的行为完全相同。

标签: java performance exception exception-handling


【解决方案1】:

finally 块引发的异常将替换从try 引发的任何异常,有关真正问题的信息可能会丢失。

由于在这种情况下允许try-finally 块抛出IOException,所以这里有一个更好的写法:

try (BufferedReader bufferedReader = Files.newBufferedReader(Paths.get("file.txt"))) {
  /* Work with `bufferedReader` */
}

当块退出时,这会自动关闭阅读器,并很好地处理任何产生的异常,即使 try 块内的代码首先抛出自己的异常,使用 Throwable 的“抑制”机制。

如果try块无异常完成,结果将是关闭资源的结果(异常与否)。如果try 块抛出异常,那将是异常结果。但如果close() 方法也引发异常,它将作为“抑制”异常添加到try 块的异常中。您可以通过编程方式对其进行询问,并且在打印堆栈跟踪时,将显示被抑制的异常,就像您可能更熟悉的“由”异常一样。

而且,您可以尝试多种资源;这些都会被关闭,并且可以抑制多个关闭异常。

这假设您正在使用文件 I/O,但相同的“try-with-resources”结构将适用于任何实现 AutoCloseable(流、SQL 对象等)的东西。

【讨论】:

  • @MarkoTopolnik 你是对的。我想我得到了正确的答案,并加强了我的好例子(我希望即使在编辑之前也不是毫无意义的)。
  • 仍然没有答案的是,对性能的影响在哪里。我实际上认为警告只是错误分类。
  • 编辑后这个答案对我来说很有意义。我认为这是一种优雅的处理方式。 @MarkoTopolnik。我在回答中回答了性能问题。我认为对性能的影响可能很小,但有趣的是,本质上重新抛出已经抛出的异常比在第一次抛出错误时处理错误效率低。另一个问题是,当您在 finally 块中使用 throw 时,在我看来,您冒着没有完成必要清理的风险。
  • @stubsthewizard 是的,所说的一切都是正确的,只是它与实际问题没有重叠。在抛出异常的方法中处理异常允许应用某些 JIT 优化,但这又与此无关,因为它与从 finally 抛出无关。您的最后一点与性能无关。总的来说,异常处理在我见过的任何应用程序中都不是性能瓶颈。什么样的代码在热循环中抛出和捕获,每秒数百万次?如果是这样,那么就是问题所在。
  • @stubsthewizard 我认为这里的术语可能存在误解:捕获异常与处理它不同。 OP 的代码不进行任何处理,它只是捕获并立即重新抛出。该成语严格来说是多余的和不好的做法,但它与性能几乎没有关系,与性能有关的一点点与finally 块没有任何关系。这个问题不是关于重新抛出,而是关于从finally 块中抛出异常对性能的假定影响。
【解决方案2】:

这不是性能问题。这是一个正确性问题。 (Marko Topolnik 关于警告被错误分类的评论对我来说似乎是正确的,我能从性能角度看到这一点的唯一方法是,如果在 try 块中抛出的异常被屏蔽,那么创建它所花费的精力和它的堆栈跟踪就被浪费了。但这距离成为最大的问题还有很长的路要走。)

不在 finally 块中抛出异常的两个原因:

  • 让 finally 块抛出异常可以掩盖 try 块中抛出的任何异常,因此您会丢失原始的有用异常,在日志中不会留下关于实际问题的线索。

  • 当您的正常控制流程因关闭时引发的异常而中断时,您可能会遇到一些暂时的 I/O 故障(您无法控制并且不会影响您的业务逻辑) ) 阻止完成一些有用的工作(例如,它可能导致当前事务回滚)。这可能取决于所涉及的资源类型;也许在某些情况下,如果关闭没有完全发生,您可能真的希望整个事情都失败,但是对于许多常见情况(如 JDBC),没有充分的理由去关心。

使用 try-with-resources 成功排除了异常屏蔽的可能性。但是,如果 try 逻辑在没有异常的情况下完成,它会传播关闭时抛出的任何内容。由于它是一种语言添加,Oracle 必须采取最保守的方法,所以在使用它时要注意它在做什么。

【讨论】:

    【解决方案3】:

    理想的解决方案:

    try (BufferedReader bufferedReader = ...) {
      //do stuff
    }
    

    但也许你使用的是 java 1.6:

    BufferedReader bufferedReader = null;
    try{
      bufferedReader = ...;
      //do stuff
    } finally {
      if (bufferedReader != null) {
        try {
          bufferedReader.close();
        } catch (final IOException ex) {
          logger.error("Problem while cleaning up.", ex);
        }
      }
    }
    

    【讨论】:

      【解决方案4】:

      在 finally 块中,您可以调用另一个传递对象 B 的方法来清理。您可以在该方法中尝试 catch finally 块。

      如果您为每个对象 A 和 B 分别清理方法,这很好,您可以在其中处理 try catch finally 块。您可以调用这两种方法来清理对象。每个对象清理的异常都是独立处理的。

      【讨论】:

        【解决方案5】:

        在给定的代码 sn-p 中,throw 语句没有任何意义。 bufferedReader.close() 方法已经抛出了异常。 catch 块应该只处理它,而不是抛出另一个异常。事实上,虽然在 finally 块中捕获抛出的异常肯定是有效的,但我真的想不出在 finally 块中抛出异常的正当理由。

        关于性能下降,从轶事的角度来看,重新抛出捕获的异常显然不如仅仅处理它有效。

        至于这将是有害的特定实例,这样的事情浮现在我的脑海中。如果你有另一种方法在你的 finally 块中执行清理,例如 FileOutputStream.close(),并且第一个抛出错误,第二个将永远不会运行,因为 throw 语句结束了块的处理。那时你会泄漏资源。

        因此,总而言之,try/catch 在 finally 块中是可以的,但为了提高效率和意外后果(内存泄漏),应避免使用 throw 语句。

        【讨论】:

          【解决方案6】:

          Finally 用于在抛出/捕获异常之后。所以按照设计,不需要在 finally 中抛出异常。这应该只在你的 try 块中完成。

          This 可能会帮助您了解有关 Java 中 try-catch-finally 异常处理的更多信息

          【讨论】:

          • finally 块总是执行,即使 try 块没有抛出异常。
          猜你喜欢
          • 2010-10-03
          • 2012-08-22
          • 2011-03-28
          • 2012-09-20
          • 1970-01-01
          • 2013-02-23
          • 1970-01-01
          • 2013-10-02
          相关资源
          最近更新 更多