由于我的新工作,我在 Java 方面做了很多工作,现在我正在研究这些微小的细节。显然,Java 代码在某种程度上是关于异常的。我想知道:
调用堆栈是否对 try-catch 块的性能有很大影响?即我是否应该避免尝试调用一个函数的函数......并且太深了?
我读到 try-catch 块只会影响异常的性能。但是,它们冒泡有多远有关系吗?
由于我的新工作,我在 Java 方面做了很多工作,现在我正在研究这些微小的细节。显然,Java 代码在某种程度上是关于异常的。我想知道:
调用堆栈是否对 try-catch 块的性能有很大影响?即我是否应该避免尝试调用一个函数的函数......并且太深了?
我读到 try-catch 块只会影响异常的性能。但是,它们冒泡有多远有关系吗?
让我们测量一下,好吗?
package tools.bench;
import java.math.BigDecimal;
public abstract class Benchmark {
final String name;
public Benchmark(String name) {
this.name = name;
}
abstract int run(int iterations) throws Throwable;
private BigDecimal time() {
try {
int nextI = 1;
int i;
long duration;
do {
i = nextI;
long start = System.nanoTime();
run(i);
duration = System.nanoTime() - start;
nextI = (i << 1) | 1;
} while (duration < 1000000000 && nextI > 0);
return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return name + "\t" + time() + " ns";
}
enum ExceptionStrategy {
none {
@Override void run() {
// do nothing
}
},
normal {
@Override void run() {
throw new RuntimeException();
}
},
withoutStackTrace {
@Override void run() {
throw new RuntimeException() {
public synchronized Throwable fillInStackTrace() {
return this;
};
};
}
};
abstract void run();
}
private static Benchmark tryBenchmark(final int depth, final ExceptionStrategy strat) {
return new Benchmark("try, depth = " + depth + ", " + strat) {
@Override int run(int iterations) {
int x = 0;
for (int i = 1; i < iterations; i++) {
try {
x += recurseAndThrow(depth);
} catch (Exception e) {
x++;
}
}
return x;
}
private int recurseAndThrow(int i) {
if (i > 0) {
return recurseAndThrow(i - 1) + 1;
} else {
strat.run();
return 0;
}
}
};
}
public static void main(String[] args) throws Exception {
int[] depths = {1, 10, 100, 1000, 10000};
for (int depth : depths) {
for (ExceptionStrategy strat : ExceptionStrategy.values()) {
System.out.println(tryBenchmark(depth, strat));
}
}
}
}
在我的(相当过时的)笔记本上,打印:
try, depth = 1, none 5.153 ns
try, depth = 1, normal 3374.113 ns
try, depth = 1, withoutStackTrace 602.570 ns
try, depth = 10, none 59.019 ns
try, depth = 10, normal 9064.392 ns
try, depth = 10, withoutStackTrace 3528.987 ns
try, depth = 100, none 604.828 ns
try, depth = 100, normal 49387.143 ns
try, depth = 100, withoutStackTrace 27968.674 ns
try, depth = 1000, none 5388.270 ns
try, depth = 1000, normal 457158.668 ns
try, depth = 1000, withoutStackTrace 271881.336 ns
try, depth = 10000, none 69793.242 ns
try, depth = 10000, normal 2895133.943 ns
try, depth = 10000, withoutStackTrace 2728533.381 ns
显然,具体结果会因您的硬件以及 JVM 实现和配置而异。但是,总体模式可能保持不变。
结论:
建议:
例外是昂贵的。使用时,会创建堆栈跟踪。如果您可以检查异常,请执行此操作。不要使用try..catch进行流量控制。当您无法检查/验证时,请使用try..catch;一个例子是做 IO 操作。
当我看到有很多 try..catch 块的代码时,我的第一反应是“这是一个糟糕的设计!”。
1.- 调用堆栈深度无需担心,Java Just In Time Compiler 将对您的代码进行优化,如方法内联,为您的代码提供最佳性能。
2.- 捕获块确实会影响性能,这是因为捕获异常可能意味着 JVM 内部的几种不同操作,例如遍历异常表、堆栈展开和常见陷阱(取消优化 JIT 的优化代码)。
3.- 作为一条建议,不要担心封装代码并最终导致几个方法调用,这会导致巨大的堆栈深度,JVM 将进行优化以获得最佳性能,当你的应用程序被编译和当它运行时,始终以代码可读性和良好的设计模式为目标,因为这将有助于使您的代码更易于维护和修改,以防某些性能修复必须启动。关于 catch 块,评估它的必要性抛出或捕获的异常,如果您正在捕获尝试捕获最常见的异常,这样您就可以避免巨大的异常表。
在调用 .printStackTrace 或 .getStackTrace 之前,不会加载堆栈跟踪。如果您在 catch 块的开头放置一个断点,您会注意到 Exception 对象的堆栈跟踪为空。