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

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


当前回答

要理解为什么不能执行优化,了解底层机制是有用的。我能找到的最简洁的例子是在http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html上用C宏实现的

#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,所以他们跳过优化,他们不能保证安全,但实现本身相当轻。

其他回答

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

try has almost no expense at all. Instead of doing the work of setting up the try at runtime, the code's metadata is structured at compile time such that when an exception is thrown, it now does a relatively expensive operation of walking up the stack and seeing if any try blocks exist that would catch this exception. From a layman's perspective, try may as well be free. It's actually throwing the exception that costs you - but unless you're throwing hundreds or thousands of exceptions, you still won't notice the cost.


Try有一些与之相关的小成本。Java不能对try块中的代码做一些它本来可以做的优化。例如,Java经常会重新安排方法中的指令,以使其运行得更快——但Java也需要保证,如果抛出异常,方法的执行就会被观察到,就像源代码中所写的语句一样,按照直到某一行的顺序执行。

Because in a try block an exception can be thrown (at any line in the try block! Some exceptions are thrown asynchronously, such as by calling stop on a Thread (which is deprecated), and even besides that OutOfMemoryError can happen almost anywhere) and yet it can be caught and code continue to execute afterwards in the same method, it is more difficult to reason about optimizations that can be made, so they are less likely to happen. (Someone would have to program the compiler to do them, reason about and guarantee correctness, etc. It'd be a big pain for something meant to be 'exceptional') But again, in practice you won't notice things like this.

我们来量一下,好吗?

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

要理解为什么不能执行优化,了解底层机制是有用的。我能找到的最简洁的例子是在http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html上用C宏实现的

#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,所以他们跳过优化,他们不能保证安全,但实现本身相当轻。

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

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