问: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编译器正确优化代码,从而降低代码的速度。我还没有测试过这个理论。


当前回答

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

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

其他回答

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

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

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

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

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

通常有形式

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
{
}

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

不要害怕例外情况

关于异常性能的好文章是:

https://shipilev.net/blog/2014/exceptional-performance/

实例化vs重用现有的,有堆栈跟踪和没有,等等:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

根据堆栈跟踪的深度:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

有关其他详细信息(包括来自JIT的x64汇编程序),请阅读原始博客文章。

这意味着Hibernate/Spring/etc-EE-shit因为异常(xD)而变慢。

通过重写应用程序控制流,避免异常(返回错误作为返回),提高应用程序的性能10 -100倍,这取决于你抛出它们的频率))

使用附带的代码,在JDK 15上,@Mecki测试用例得到了完全不同的结果。这基本上是在5个循环中运行代码,第一个循环稍微短一些,给VM一些时间来热身。

结果:

Loop 1 10000 cycles
method1 took 1 ms, result was 2
method2 took 0 ms, result was 2
method3 took 22 ms, result was 2
method4 took 22 ms, result was 2
method5 took 24 ms, result was 2
Loop 2 10000000 cycles
method1 took 39 ms, result was 2
method2 took 39 ms, result was 2
method3 took 1558 ms, result was 2
method4 took 1640 ms, result was 2
method5 took 1717 ms, result was 2
Loop 3 10000000 cycles
method1 took 49 ms, result was 2
method2 took 48 ms, result was 2
method3 took 126 ms, result was 2
method4 took 88 ms, result was 2
method5 took 87 ms, result was 2
Loop 4 10000000 cycles
method1 took 34 ms, result was 2
method2 took 34 ms, result was 2
method3 took 33 ms, result was 2
method4 took 98 ms, result was 2
method5 took 58 ms, result was 2
Loop 5 10000000 cycles
method1 took 34 ms, result was 2
method2 took 33 ms, result was 2
method3 took 33 ms, result was 2
method4 took 48 ms, result was 2
method5 took 49 ms, result was 2
package hs.jfx.eventstream.api;

public class Snippet {
  int value;


  public int getValue() {
      return value;
  }

  public void reset() {
      value = 0;
  }

  // Calculates without exception
  public void method1(int i) {
      value = ((value + i) / i) << 1;
      // Will never be true
      if ((i & 0xFFFFFFF) == 1000000000) {
          System.out.println("You'll never see this!");
      }
  }

  // Could in theory throw one, but never will
  public void method2(int i) throws Exception {
      value = ((value + i) / i) << 1;
      // Will never be true
      if ((i & 0xFFFFFFF) == 1000000000) {
          throw new Exception();
      }
  }

  private static final NoStackTraceRuntimeException E = new NoStackTraceRuntimeException();

  // This one will regularly throw one
  public void method3(int i) throws NoStackTraceRuntimeException {
      value = ((value + i) / i) << 1;
      // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
      // an AND operation between two integers. The size of the number plays
      // no role. AND on 32 BIT always ANDs all 32 bits
      if ((i & 0x1) == 1) {
          throw E;
      }
  }

  // This one will regularly throw one
  public void method4(int i) throws NoStackTraceThrowable {
      value = ((value + i) / i) << 1;
      // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
      // an AND operation between two integers. The size of the number plays
      // no role. AND on 32 BIT always ANDs all 32 bits
      if ((i & 0x1) == 1) {
          throw new NoStackTraceThrowable();
      }
  }

  // This one will regularly throw one
  public void method5(int i) throws NoStackTraceRuntimeException {
      value = ((value + i) / i) << 1;
      // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
      // an AND operation between two integers. The size of the number plays
      // no role. AND on 32 BIT always ANDs all 32 bits
      if ((i & 0x1) == 1) {
          throw new NoStackTraceRuntimeException();
      }
  }

  public static void main(String[] args) {
    for(int k = 0; k < 5; k++) {
      int cycles = 10000000;
      if(k == 0) {
        cycles = 10000;
        try {
          Thread.sleep(500);
        }
        catch(InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }
      System.out.println("Loop " + (k + 1) + " " + cycles + " cycles");
      int i;
      long l;
      Snippet t = new Snippet();

      l = System.currentTimeMillis();
      t.reset();
      for (i = 1; i < cycles; i++) {
          t.method1(i);
      }
      l = System.currentTimeMillis() - l;
      System.out.println(
          "method1 took " + l + " ms, result was " + t.getValue()
      );

      l = System.currentTimeMillis();
      t.reset();
      for (i = 1; i < cycles; i++) {
          try {
              t.method2(i);
          } catch (Exception e) {
              System.out.println("You'll never see this!");
          }
      }
      l = System.currentTimeMillis() - l;
      System.out.println(
          "method2 took " + l + " ms, result was " + t.getValue()
      );

      l = System.currentTimeMillis();
      t.reset();
      for (i = 1; i < cycles; i++) {
          try {
              t.method3(i);
          } catch (NoStackTraceRuntimeException e) {
            // always comes here
          }
      }
      l = System.currentTimeMillis() - l;
      System.out.println(
          "method3 took " + l + " ms, result was " + t.getValue()
      );


      l = System.currentTimeMillis();
      t.reset();
      for (i = 1; i < cycles; i++) {
          try {
              t.method4(i);
          } catch (NoStackTraceThrowable e) {
            // always comes here
          }
      }
      l = System.currentTimeMillis() - l;
      System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


      l = System.currentTimeMillis();
      t.reset();
      for (i = 1; i < cycles; i++) {
          try {
              t.method5(i);
          } catch (RuntimeException e) {
            // always comes here
          }
      }
      l = System.currentTimeMillis() - l;
      System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
    }
  }

  public static class NoStackTraceRuntimeException extends RuntimeException {
    public NoStackTraceRuntimeException() {
        super("my special throwable", null, false, false);
    }
  }

  public static class NoStackTraceThrowable extends Throwable {
    public NoStackTraceThrowable() {
        super("my special throwable", null, false, false);
    }
  }
}