抛出Exception的哪个部分很贵?

在Java中,当实际上没有错误时使用throw / catch作为逻辑的一部分通常是一个坏主意(部分),因为抛出和捕获异常很昂贵,并且在循环中多次执行通常比其他方法慢得多控制结构不涉及抛出异常。

我的问题是,在throw / catch本身或创建Exception对象(因为它获取大量运行时信息,包括执行堆栈)时会产生成本吗?

换句话说,如果我这样做

Exception e = new Exception();

但是不要扔掉它,是投掷的大部分成本,还是抛出+捕获处理什么是昂贵的?

我不问是否将代码放在try / catch块中会增加执行代码的代价,我问是否捕获Exception是昂贵的部分,还是创建(调用构造函数)Exception是昂贵的部分。

另一个问这个问题的方法是,如果我创建了Exception的一个实例,并且一遍又一遍地抛出并捕获它,那么每次抛出时会比创建一个新的异常快得多吗?


创建一个异常对象并不比创建其他常规对象更昂贵。 主要成本隐藏在本地fillInStackTrace方法中,该方法遍历调用堆栈并收集所有需要的信息以构建堆栈跟踪:类,方法名称,行号等。

关于高异常成本的神话来自大多数Throwable函数隐式调用fillInStackTrace 。 但是,有一个构造函数可以在没有堆栈跟踪的情况下创建Throwable 。 它可以让你创建非常快的throwables来实例化。 创建轻量级异常的另一种方法是重写fillInStackTrace


现在抛出异常呢?
事实上,这取决于地方抛出的异常被捕获

如果它被捕获到相同的方法(或者更确切地说,在相同的上下文中,由于内联可以包含几种方法),那么throw就像goto一样快速简单(当然,在JIT编译之后)。

但是,如果catch块位于堆栈中较深的地方,那么JVM需要展开堆栈帧,而这可能需要更长的时间。 如果涉及synchronized块或方法,则需要更长的时间,因为展开意味着释放已移除堆栈帧所拥有的监视器。


我可以通过适当的基准确认上述说法,但幸运的是,我不需要这样做,因为HotSpot性能工程师AlexeyShipilëv的后续工作已经完全涵盖了所有方面:Lil例外的卓越性能。


大多数Throwable函数中的第一个操作是填充堆栈跟踪,这是大部分花费的地方。

但是,有一个带有标志的受保护的构造函数来禁用堆栈跟踪。 这个构造函数在扩展Exception时也是可访问的。 如果您创建自定义异常类型,则可以避免创建堆栈跟踪,并以牺牲较少的信息为代价获得更好的性能。

如果通过普通方法创建任何类型的单个异常,则可以多次重新抛出它,而无需填充堆栈跟踪的开销。 但是,它的堆栈跟踪将反映它的构建位置,而不是它在特定实例中的位置。

Java的当前版本会尝试优化堆栈跟踪创建。 调用本地代码以填充堆栈跟踪,该跟踪将跟踪记录在轻量级本地结构中。 只有在printStackTrace() getStackTrace()printStackTrace()或其他需要跟踪的方法时,才会从此记录中延迟创建相应的Java StackTraceElement对象。

如果消除堆栈跟踪生成,另一个主要成本就是在抛出和捕获之间展开堆栈。 在发现异常之前遇到的介入帧越少,这将会越快。

设计你的程序,以便仅在真正例外的情况下抛出异常,像这样的优化很难证明是合理的。


Theres在这里写了Exceptions。

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

结论是堆栈跟踪结构和堆叠展开是昂贵的部分。 下面的代码利用了1.7中的一个特性,我们可以打开和关闭堆栈轨迹。 然后,我们可以使用它来查看不同场景的成本

以下是单独创建对象的时间。 我在这里添加了String ,所以你可以看到没有写入堆栈,创建一个JavaException对象和一个String几乎没有区别。 随着堆书写打开,差异是剧烈的,即至少一个数量级慢。

Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with    stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)

以下显示从一次特定深度的投掷回归一百万次需要多长时间。

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|           1428|             243| 588 (%)|
|   15|           1763|             393| 449 (%)|
|   14|           1746|             390| 448 (%)|
|   13|           1703|             384| 443 (%)|
|   12|           1697|             391| 434 (%)|
|   11|           1707|             410| 416 (%)|
|   10|           1226|             197| 622 (%)|
|    9|           1242|             206| 603 (%)|
|    8|           1251|             207| 604 (%)|
|    7|           1213|             208| 583 (%)|
|    6|           1164|             206| 565 (%)|
|    5|           1134|             205| 553 (%)|
|    4|           1106|             203| 545 (%)|
|    3|           1043|             192| 543 (%)| 

以下几乎肯定是一个粗略的简化...

如果我们在堆栈写入时深度为16,那么对象创建大约需要40%的时间,实际的堆栈跟踪占绝大多数。 实例化JavaException对象的〜93%是由于采取了堆栈跟踪。 这意味着在这种情况下展开堆叠占用另外50%的时间。

当我们关闭堆栈跟踪对象创建帐户的时间更小,即20%时,堆栈展开时间占80%。

在这两种情况下,堆叠放卷占用了整个时间的很大一部分时间。

public class JavaException extends Exception {
  JavaException(String reason, int mode) {
    super(reason, null, false, false);
  }
  JavaException(String reason) {
    super(reason);
  }

  public static void main(String[] args) {
    int iterations = 1000000;
    long create_time_with    = 0;
    long create_time_without = 0;
    long create_string = 0;
    for (int i = 0; i < iterations; i++) {
      long start = System.nanoTime();
      JavaException jex = new JavaException("testing");
      long stop  =  System.nanoTime();
      create_time_with += stop - start;

      start = System.nanoTime();
      JavaException jex2 = new JavaException("testing", 1);
      stop = System.nanoTime();
      create_time_without += stop - start;

      start = System.nanoTime();
      String str = new String("testing");
      stop = System.nanoTime();
      create_string += stop - start;

    }
    double interval_with    = ((double)create_time_with)/1000000;
    double interval_without = ((double)create_time_without)/1000000;
    double interval_string  = ((double)create_string)/1000000;

    System.out.printf("Time to create %d String objects: %.2f (ms)n", iterations, interval_string);
    System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)n", iterations, interval_with);
    System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)n", iterations, interval_without);

    JavaException jex = new JavaException("testing");
    int depth = 14;
    int i = depth;
    double[] with_stack    = new double[20];
    double[] without_stack = new double[20];

    for(; i > 0 ; --i) {
      without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
      with_stack[i]    = jex.timerLoop(i, iterations, 1)/1000000;
    }
    i = depth;
    System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|n");
    for(; i > 0 ; --i) {
      double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
      System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| n", i + 2, with_stack[i] , without_stack[i], ratio);
      //System.out.printf("%dt%.2f (ms)n", i, ratio);
    }
  }
 private int thrower(int i, int mode) throws JavaException {
    ExArg.time_start[i] = System.nanoTime();
    if(mode == 0) { throw new JavaException("without stack", 1); }
    throw new JavaException("with stack");
  }
  private int catcher1(int i, int mode) throws JavaException{
    return this.stack_of_calls(i, mode);
  }
  private long timerLoop(int depth, int iterations, int mode) {
    for (int i = 0; i < iterations; i++) {
      try {
        this.catcher1(depth, mode);
      } catch (JavaException e) {
        ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
      }
    }
    //long stop = System.nanoTime();
    return ExArg.time_accum[depth];
  }

  private int bad_method14(int i, int mode) throws JavaException  {
    if(i > 0) { this.thrower(i, mode); }
    return i;
  }
  private int bad_method13(int i, int mode) throws JavaException  {
    if(i == 13) { this.thrower(i, mode); }
    return bad_method14(i,mode);
  }
  private int bad_method12(int i, int mode) throws JavaException{
    if(i == 12) { this.thrower(i, mode); }
    return bad_method13(i,mode);
  }
  private int bad_method11(int i, int mode) throws JavaException{
    if(i == 11) { this.thrower(i, mode); }
    return bad_method12(i,mode);
  }
  private int bad_method10(int i, int mode) throws JavaException{
    if(i == 10) { this.thrower(i, mode); }
    return bad_method11(i,mode);
  }
  private int bad_method9(int i, int mode) throws JavaException{
    if(i == 9) { this.thrower(i, mode); }
    return bad_method10(i,mode);
  }
  private int bad_method8(int i, int mode) throws JavaException{
    if(i == 8) { this.thrower(i, mode); }
    return bad_method9(i,mode);
  }
  private int bad_method7(int i, int mode) throws JavaException{
    if(i == 7) { this.thrower(i, mode); }
    return bad_method8(i,mode);
  }
  private int bad_method6(int i, int mode) throws JavaException{
    if(i == 6) { this.thrower(i, mode); }
    return bad_method7(i,mode);
  }
  private int bad_method5(int i, int mode) throws JavaException{
    if(i == 5) { this.thrower(i, mode); }
    return bad_method6(i,mode);
  }
  private int bad_method4(int i, int mode) throws JavaException{
    if(i == 4) { this.thrower(i, mode); }
    return bad_method5(i,mode);
  }
  protected int bad_method3(int i, int mode) throws JavaException{
    if(i == 3) { this.thrower(i, mode); }
    return bad_method4(i,mode);
  }
  private int bad_method2(int i, int mode) throws JavaException{
    if(i == 2) { this.thrower(i, mode); }
    return bad_method3(i,mode);
  }
  private int bad_method1(int i, int mode) throws JavaException{
    if(i == 1) { this.thrower(i, mode); }
    return bad_method2(i,mode);
  }
  private int stack_of_calls(int i, int mode) throws JavaException{
    if(i == 0) { this.thrower(i, mode); }
    return bad_method1(i,mode);
  }
}

class ExArg {
  public static long[] time_start;
  public static long[] time_accum;
  static {
     time_start = new long[20];
     time_accum = new long[20];
  };
}

这个例子中的堆栈帧与通常找到的相比很小。

您可以使用javap查看字节码

javap -c -v -constants JavaException.class

即这是方法4 ...

   protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
  stack=3, locals=3, args_size=3
     0: iload_1       
     1: iconst_3      
     2: if_icmpne     12
     5: aload_0       
     6: iload_1       
     7: iload_2       
     8: invokespecial #6                  // Method thrower:(II)I
    11: pop           
    12: aload_0       
    13: iload_1       
    14: iload_2       
    15: invokespecial #17                 // Method bad_method4:(II)I
    18: ireturn       
  LineNumberTable:
    line 63: 0
    line 64: 12
  StackMapTable: number_of_entries = 1
       frame_type = 12 /* same */

Exceptions:
  throws JavaException
链接地址: http://www.djcxy.com/p/76057.html

上一篇: Which part of throwing an Exception is expensive?

下一篇: Java NullPointerException on conditional null check