我们知道捕获异常的代价很高。但是,即使从未抛出异常,在Java中使用try-catch块的代价也很高吗?

我发现Stack Overflow的问题/答案为什么尝试块昂贵?,但它是用于。net的。


当前回答

另一个微基准测试(来源)。

我创建了一个测试,在这个测试中,我根据异常百分比来测量尝试捕获和不尝试捕获的代码版本。10%百分比意味着10%的测试用例除以零用例。在一种情况下,它由try-catch块处理,在另一种情况下由条件操作符处理。这是我的结果表:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Percentage | Result (try/if, ns)   
    0%     |      88/90   
    1%     |      89/87    
    10%    |      86/97    
    90%    |      85/83   

这表明这些案例之间没有显著差异。

其他回答

另一个微基准测试(来源)。

我创建了一个测试,在这个测试中,我根据异常百分比来测量尝试捕获和不尝试捕获的代码版本。10%百分比意味着10%的测试用例除以零用例。在一种情况下,它由try-catch块处理,在另一种情况下由条件操作符处理。这是我的结果表:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Percentage | Result (try/if, ns)   
    0%     |      88/90   
    1%     |      89/87    
    10%    |      86/97    
    90%    |      85/83   

这表明这些案例之间没有显著差异。

我发现捕获NullPointException非常昂贵。对于1.2k操作,当我以同样的方式处理if(object==null)时,时间为200ms和12ms,这对我来说是很大的改进。

尝试/捕获可能会对性能产生一些影响。这是因为它阻止JVM进行一些优化。约书亚·布洛赫在《有效的Java》一书中说:

•将代码放置在try-catch块中会抑制现代JVM实现可能执行的某些优化。

我们来量一下,好吗?

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所说:“过早的优化是万恶之源”。

是的,正如其他人所说,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
}

不是“扫描”。