在Java中,在没有实际错误的情况下,将抛出/捕获作为逻辑的一部分通常是个坏主意(部分原因是,抛出和捕获异常的代价很高,而且在循环中多次执行通常要比其他不涉及抛出异常的控制结构慢得多。
我的问题是,抛出/捕获本身或创建异常对象(因为它获得了许多运行时信息,包括执行堆栈)所产生的成本?
换句话说,如果我做了
Exception e = new Exception();但是不要扔它,这是投掷的大部分成本,还是抛出+捕捉处理的代价是昂贵的?
我并不是问将代码放入try/catch块是否会增加执行该代码的成本,而是询问捕获异常是昂贵的部分,还是创建(调用构造函数)--异常是昂贵的部分。
另一种问这个问题的方法是,如果我创建了一个异常实例,并一次又一次地抛出并捕获了它,那么这比每次抛出时创建一个新异常要快得多吗?
发布于 2016-03-31 21:35:28
创建异常对象的不一定比创建其他常规对象更昂贵。主要成本隐藏在本机fillInStackTrace方法中,该方法遍历调用堆栈并收集构建堆栈跟踪所需的所有信息:类、方法名称、行号等。
大多数Throwable构造函数都隐式调用fillInStackTrace。这就是创建异常速度慢的原因。但是,有一个构造函数可以创建一个没有堆栈跟踪的Throwable。它允许您使throwables非常快地实例化。另一种创建轻量级异常的方法是重写fillInStackTrace。
现在,抛出异常如何?
实际上,这取决于抛出的异常在何处是捕捉到的。
如果它被捕获在相同的方法中(或者更准确地说,是在相同的上下文中,因为上下文可以包含几个由于内联而导致的方法),那么throw就和goto一样快速和简单(当然,在JIT编译之后)。
但是,如果catch块位于堆栈的更深的位置,那么JVM需要对堆栈帧进行解压,这可能会花费更长的时间。如果涉及到synchronized块或方法,则需要更长的时间,因为展开意味着释放已删除的堆栈帧所拥有的监视器。
我可以通过适当的基准测试来确认上述声明,但幸运的是,我不需要这样做,因为HotSpot的性能工程师Alexey:Lil异常的特殊表现已经完美地涵盖了所有方面。
发布于 2016-03-31 21:20:37
大多数Throwable构造函数中的第一个操作是填写堆栈跟踪,,这是大部分开销所在。
但是,有一个带标志的受保护构造函数来禁用堆栈跟踪。扩展这个构造函数时也可以访问Exception。如果创建自定义异常类型,则可以避免堆栈跟踪创建,并以减少信息为代价获得更好的性能。
如果以正常方式创建任何类型的单个异常,则可以多次重新抛出它,而无需填充堆栈跟踪的开销。但是,它的堆栈跟踪将反映它的构造位置,而不是在特定实例中抛出的位置。
当前版本的Java尝试优化堆栈跟踪创建。调用本机代码来填充堆栈跟踪,堆栈跟踪以较轻的本机结构记录跟踪。只有在调用StackTraceElement、printStackTrace()或其他需要跟踪的方法时,才会延迟从此记录创建相应的Java对象。
如果消除堆栈跟踪生成,则另一个主要成本是在抛出和捕获之间展开堆栈。异常捕获之前遇到的中间帧越少,就越快。
设计程序时,只有在真正异常的情况下才会抛出异常,这样的优化很难被证明是合理的。
发布于 2016-03-31 21:46:44
这里有一个很好的例外记录。
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%的时间,那么实际的堆栈跟踪就占了绝大部分。~93%的JavaException对象实例化是由于正在进行堆栈跟踪。这意味着在这种情况下展开堆栈占用了其他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("%d\t%.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 JavaExceptionhttps://stackoverflow.com/questions/36343209
复制相似问题