我们知道捕获异常非常昂贵。但是,即使从不抛出异常,在Java中使用try-catch块是否也很昂贵?
我发现了堆栈溢出问题/答案为什么尝试块价格昂贵?,但适用于.NET。
try { /* do stuff */ } finally { /* make sure to release resources */ }
是合法且有用的
finally
使用try-with-resources
我们知道捕获异常非常昂贵。但是,即使从不抛出异常,在Java中使用try-catch块是否也很昂贵?
我发现了堆栈溢出问题/答案为什么尝试块价格昂贵?,但适用于.NET。
try { /* do stuff */ } finally { /* make sure to release resources */ }
是合法且有用的
finally
使用try-with-resources
Answers:
try
几乎没有任何花销。try
代码的元数据不是在运行时进行设置,而是在编译时进行结构化,这样,当引发异常时,它现在执行相对昂贵的操作,即遍历堆栈并查看是否try
存在任何块可以捕获此异常。例外。从外行的角度来看,它try
可能也是免费的。它实际上是在抛出导致您付出代价的异常-但是,除非您抛出数百或数千个异常,否则您仍然不会注意到成本。
try
有一些与此相关的小费用。Java无法对代码try
块中的代码进行其他优化,而在其他方面则无法做到。例如,Java经常会重新安排方法中的指令以使其运行更快-但是Java还需要保证,如果引发异常,则将观察该方法的执行,就像执行源代码中编写的语句一样为了达到某条线。
因为在一个try
块中可以抛出一个异常(在try块的任何行上!有些异常是异步抛出的,例如通过调用stop
一个Thread(已弃用),甚至OutOfMemoryError几乎可以在任何地方发生),但是它可以捕获并以相同的方法继续执行代码,因此很难对可以进行的优化进行推理,因此不太可能发生优化。(有些人必须对编译器进行编程,以进行推理,保证正确性等。对于要成为“例外”的东西,这将是一个巨大的痛苦。)但是,实际上,您不会注意到这样的事情。
try...finally
块catch
也会阻止一些优化吗?
Exception
对象是大多数时间的过程。
我们来衡量一下吧?
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 < 100000000 && 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";
}
public static void main(String[] args) throws Exception {
Benchmark[] benchmarks = {
new Benchmark("try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
try {
x += i;
} catch (Exception e) {
e.printStackTrace();
}
}
return x;
}
}, new Benchmark("no try") {
@Override int run(int iterations) throws Throwable {
int x = 0;
for (int i = 0; i < iterations; i++) {
x += i;
}
return x;
}
}
};
for (Benchmark bm : benchmarks) {
System.out.println(bm);
}
}
}
在我的计算机上,打印的内容如下:
try 0.598 ns
no try 0.601 ns
至少在这个简单的示例中,try语句对性能没有可测量的影响。随意测量更复杂的参数。
一般而言,我建议您不必担心语言构造的性能成本,除非您有证据证明代码中存在实际的性能问题。或正如Donald Knuth 所说:“过早的优化是万恶之源”。
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
,实际上,在生成的本机代码中同时存在循环和循环。不,抽象方法没有内联,因为它们的调用者不是及时地编译的(大概是因为没有被足够多次地调用)。
try
/ catch
可能会对性能产生影响。这是因为它阻止JVM进行一些优化。约书亚·布洛赫(Joshua Bloch)在《有效的Java》中说:
•将代码放置在try-catch块中会禁止现代JVM实现可能执行的某些优化。
是的,正如其他人所说的那样,一个try
块禁止{}
围绕其周围的字符进行某些优化。特别是,优化器必须假设该块内的任何点都可能发生异常,因此无法保证语句会被执行。
例如:
try {
int x = a + b * c * d;
other stuff;
}
catch (something) {
....
}
int y = a + b * c * d;
use y somehow;
如果不使用try
,则计算得出的要分配给的值x
可以另存为“公共子表达式”,并重新用于分配给y
。但是由于try
不能保证第一个表达式曾经被计算过,因此必须重新计算该表达式。在“直线”代码中,这通常不是什么大问题,但在循环中可能很重要。
但是应注意,这仅适用于JITCed代码。javac仅进行少量优化,字节码解释器进入/离开try
块的成本为零。(没有生成字节码来标记块边界。)
并且最好的:
public class TryFinally {
public static void main(String[] argv) throws Throwable {
try {
throw new Throwable();
}
finally {
System.out.println("Finally!");
}
}
}
输出:
C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
at TryFinally.main(TryFinally.java:4)
javap输出:
C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
public TryFinally();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Throwable;
Code:
0: new #2 // class java/lang/Throwable
3: dup
4: invokespecial #3 // Method java/lang/Throwable."<init>":()V
7: athrow
8: astore_1
9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #5 // String Finally!
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: aload_1
18: athrow
Exception table:
from to target type
0 9 8 any
}
没有“ GOTO”。
catch/finally
帧中。
finally
字节码中没有,try/catch(Throwable any){...; throw any;}
它的确有catch语句w / frame和Throwable必须定义(非null),依此类推。您为什么要争论这个话题,您至少可以检查一些字节码?展示的当前指南。最后的方法是复制块并避免使用goto节(先前的impl),但是必须根据字节数来复制字节码。
要了解为什么无法执行优化,了解基本机制很有用。我可以找到的最简洁的示例是在C宏中实现的:http : //www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html
#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)
编译器通常很难确定是否可以将跳转本地化为X,Y和Z,因此它们会跳过无法保证安全的优化,但是实现本身比较轻便。
另一个微基准测试(来源)。
我创建了一个测试,在该测试中,我基于异常百分比来评估try-catch和no-try-catch代码版本。10%百分比表示10%的测试用例已被零个案例除。在一种情况下,它由try-catch块处理,在另一种情况下,由条件运算符处理。这是我的结果表:
OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
百分比 结果(尝试/如果,ns) 0%| 88/90 1%| 89/87 10%| 86/97 90%| 85/83
也就是说,这些情况之间没有显着差异。
我发现捕获NullPointException非常昂贵。对于1.2k的操作,我以相同的方式处理if(object==null)
它的时间分别为200ms和12ms,这对我来说是相当不错的进步。