问:Java中的异常处理真的很慢吗?

传统观点以及大量谷歌结果表明,不应该将异常逻辑用于Java中的正常程序流。通常会给出两个原因,

它真的很慢——甚至比普通代码慢一个数量级(给出的原因各不相同),

and

它很混乱,因为人们只希望在异常代码中处理错误。

这个问题是关于第一条的。

As an example, this page describes Java exception handling as "very slow" and relates the slowness to the creation of the exception message string - "this string is then used in creating the exception object that is thrown. This is not fast." The article Effective Exception Handling in Java says that "the reason for this is due to the object creation aspect of exception handling, which thereby makes throwing exceptions inherently slow". Another reason out there is that the stack trace generation is what slows it down.

My testing (using Java 1.6.0_07, Java HotSpot 10.0, on 32 bit Linux), indicates that exception handling is no slower than regular code. I tried running a method in a loop that executes some code. At the end of the method, I use a boolean to indicate whether to return or throw. This way the actual processing is the same. I tried running the methods in different orders and averaging my test times, thinking it may have been the JVM warming up. In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). I am completely open to the possibility that my tests were wrong, but I haven't seen anything out there in the way of the code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow.

引导我走上这条路的是我需要使用的一个API,它将抛出异常作为正常控制逻辑的一部分。我想纠正它们的用法,但现在我可能做不到。我是否应该赞美他们的前瞻思维?

在论文《即时编译中的高效Java异常处理》中,作者建议,即使没有抛出异常,仅异常处理程序的存在就足以阻止JIT编译器正确优化代码,从而降低代码的速度。我还没有测试过这个理论。


当前回答

供你参考,我扩展了Mecki做的实验:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

前3个和Mecki的一样(我的笔记本电脑明显慢一些)。

method4和method3是一样的,除了它创建了一个新的Integer(1)而不是抛出一个新的Exception()。

method5类似于method3,除了它创建了新的Exception()而不抛出它。

Method6和method3很像,只是它会抛出一个预先创建的异常(一个实例变量),而不是创建一个新异常。

在Java中,抛出异常的大部分开销是收集堆栈跟踪所花费的时间,这发生在创建异常对象时。抛出异常的实际成本虽然很大,但比创建异常的成本要小得多。

其他回答

为什么异常回报率会比正常回报率慢呢?

只要不将堆栈跟踪输出到终端,将其保存到一个文件或类似的文件中,catch块就不会比其他代码块做更多的工作。所以,我无法想象为什么“throw new my_cool_error()”应该这么慢。

好问题,我期待关于这个话题的进一步信息!

即使抛出异常并不慢,对于正常的程序流抛出异常仍然是一个坏主意。使用这种方式,它是类似于GOTO…

我想这并没有真正回答问题。我想抛出异常的“传统”智慧在早期的java版本(< 1.4)中是正确的。创建异常需要虚拟机创建整个堆栈跟踪。从那时起,在VM中发生了很多变化,以加快速度,这可能是已经改进的一个领域。

不知道这些主题是否相关,但我曾经想实现一个依赖于当前线程的堆栈跟踪的技巧:我想发现方法的名称,它触发了实例化类中的实例化(是的,这个想法很疯狂,我完全放弃了它)。所以我发现调用Thread.currentThread(). getstacktrace()是非常慢的(由于本机的dumpThreads方法,它在内部使用)。

相应地,Java Throwable有一个本地方法fillInStackTrace。我认为前面描述的kill -catch块以某种方式触发了该方法的执行。

但让我告诉你另一个故事……

在Scala中,一些函数特性是使用ControlThrowable在JVM中编译的,它扩展了Throwable,并以以下方式覆盖了它的fillInStackTrace:

override def fillInStackTrace(): Throwable = this

所以我调整了上面的测试(循环量减少了十,我的机器有点慢:):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

所以,结果是:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

你看,method3和method4之间唯一的区别是它们会抛出不同类型的异常。是的,method4仍然比method1和method2慢,但是差异是可以接受的。

HotSpot非常能够删除系统生成的异常代码,只要它是内联的。但是,显式创建的异常和其他未删除的异常要花费大量时间来创建堆栈跟踪。重写fillInStackTrace以查看这会如何影响性能。

我对异常速度和以编程方式检查数据的看法。

许多类都有字符串到值的转换器(扫描器/解析器),也有受人尊敬和知名的库;)

通常有形式

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

异常名称只是例子,通常是未选中的(运行时),所以抛出声明只是我的图片

有时存在第二种形式:

public static Example Parse(String input, Example defaultValue)

不扔

当第二个文件不可用时(或者程序员读的文档太少,只使用第一个文件),用正则表达式编写这样的代码。正则表达式很酷,政治正确等:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

使用这段代码,程序员没有异常成本。BUT具有相当高的代价的正则表达式ALWAYS与小的代价异常有时。

我几乎总是在这种情况下使用

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

没有分析堆栈跟踪等,我相信在你的讲座后相当快。

不要害怕例外情况