我们知道捕获异常很昂贵。但是,即使没有抛出异常,使用Java中的try-catch块是否也很昂贵?
我在Stack Overflow上找到了一个问题/答案为什么 try 块很昂贵? ,但它是针对.NET的。
我们知道捕获异常很昂贵。但是,即使没有抛出异常,使用Java中的try-catch块是否也很昂贵?
我在Stack Overflow上找到了一个问题/答案为什么 try 块很昂贵? ,但它是针对.NET的。
try
几乎没有任何开销。它不是在运行时设置try
,而是在编译时结构化代码的元数据,以使得当抛出异常时,它现在执行相对昂贵的操作:遍历堆栈并查看是否存在任何能够捕获此异常的 try
块。从外行人的角度来看,try
几乎就像是免费的。实际上,你需要付出代价的是抛出异常,但除非你抛出了数百或数千个异常,否则你仍然不会注意到这个开销。
try
有一些轻微的成本与之相关联。Java不能对 try
块中的代码进行某些优化。例如,Java通常会重新排列方法中的指令以使其运行更快 - 但Java也需要保证如果抛出异常,则该方法的执行必须被观察为执行源代码中按顺序编写的语句达到某个行。
因为在 try
块中可能会抛出异常(在块中的任何一行!有些异常是异步抛出的,例如通过调用线程的 stop
(已弃用), 除此之外,OutOfMemoryError 几乎可以发生在任何地方),但它仍然可以被捕获并且在同一个方法中继续执行,所以更难推理可行的优化方法,因此它们不太可能发生(某人必须编写编译器来执行这些操作,推理和保证正确性等。对于要“异常”的事情来说,这将是一个巨大的痛苦)。但是,在实践中,您不会注意到这些事情。
catch
的try...finally
块是否也会阻止一些优化? - dajood让我们来测量一下,好吗?
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
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
,生成的本地代码中确实包含循环和其中的加法。抽象方法没有被内联,因为它们的调用者没有被即时编译(可能是因为调用次数不够)。 - meritontry
/catch
可能会对性能产生一定的影响,因为它阻止了JVM进行某些优化。《Effective Java》一书中,Joshua Bloch指出:
• 将代码放在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
,无法保证第一个表达式已经被计算过,因此必须重新计算该表达式。在“直线”代码中,这通常不是什么大问题,但在循环中可能会产生明显的影响。
然而需要指出的是,这仅适用于经过JIT编译的代码。javac只进行了微不足道的优化,对于字节码解释器进入/退出try
块没有任何成本。(没有生成字节码来标记块边界。)
还有对于bestsss:
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
框架中。 - bestsssfinally
,而是try/catch(Throwable any){...; throw any;}
。它确实有带有框架和必须定义(非空)的Throwable的catch语句等等。为什么要争论这个话题,你可以至少检查一些字节码吗?当前实现finally的指南是复制块并避免goto部分(以前的实现),但是字节码必须根据有多少出口点进行复制。 - bestsss又一个微基准测试(源代码)。
我创建了一个测试,用于测量基于异常百分比的try-catch和无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
这表明在这些情况下没有显着的差异。
#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)
我发现捕获NullPointException的代价相当昂贵。对于1.2k次操作,时间约为200毫秒,但当我采用同样的方式处理时,即if(object==null)
,时间减少到了12毫秒,这对我来说是相当大的改进。
try { /* do stuff */ } finally { /* make sure to release resources */ }
是合法且有用的代码结构。 - A4L