高性能要求下异常对性能影响

主题

高性能要求下异常对性能影响

对比方案

普通接入对比特殊处理:

  • 默认实现异常
  • 特殊处理 fillInStackTrace 方法

异常代码

NotFilledException.java

public class NotFilledException extends Exception {
@Override
public Throwable fillInStackTrace() {
return this;
}
}

FilledException.java

public class FilledException extends Exception {
}

JMH 测试代码

...
@Fork(value = 3)
@BenchmarkMode({Mode.Throughput})
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 8, time = 1)
@Threads(value = 8)
@State(Scope.Benchmark)
public class FilledExceptionBenchmark {
@Benchmark
public void fill(Blackhole blackhole) {
blackhole.consume(new FilledException());
}

@Benchmark
public void notFill(Blackhole blackhole) {
blackhole.consume(new NotFilledException());
}
}

测试结果

Benchmark Mode Cnt Score Error Units
fill thrpt 24 1709917.327 ± 30294.221 ops/s
notFill thrpt 24 216433082.224 ± 17207092.730 ops/s

解读

public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}

private native Throwable fillInStackTrace(int dummy);

这是 Throw.java 代码内部实现。可以看到:

  1. 存在关键字 synchronized,明显多线程下会有阻塞点。而且不是一个异常的问题,是所有异常,只要调用这个方法,都会阻塞。
  2. 存在关键字 native,表明实际上是需要 JVM 将堆栈数据从 C++ 层面上读取出来注入 Java。这也是耗时操作。
  3. 处理异常是一个平衡,要在事后追查和性能间获得平衡。普通项目不用在意此事,高性能项目需要考虑这些问题。