在Java中,try-catch块应该有多深?

在Java中,try-catch块应该有多深?,java,performance,try-catch,Java,Performance,Try Catch,由于我的新工作,我在Java方面做了很多工作,现在我正在深入了解一些小细节。显然,Java代码在某种程度上是关于异常的。我想知道: 调用堆栈对try-catch块的性能影响很大吗?也就是说,我应该避免尝试调用以下函数的函数:。。。太深了 我读到try-catch块只影响异常时的性能。然而,它们泡沫化的程度有关系吗?例外情况是昂贵的。使用时,将创建堆栈跟踪。如果可以检查异常,请执行此操作。不要使用try..catch进行流量控制。无法检查/验证时,请使用try..catch;例如,执行IO操作 当

由于我的新工作,我在Java方面做了很多工作,现在我正在深入了解一些小细节。显然,Java代码在某种程度上是关于异常的。我想知道:

调用堆栈对try-catch块的性能影响很大吗?也就是说,我应该避免尝试调用以下函数的函数:。。。太深了


我读到try-catch块只影响异常时的性能。然而,它们泡沫化的程度有关系吗?

例外情况是昂贵的。使用时,将创建堆栈跟踪。如果可以检查异常,请执行此操作。不要使用try..catch进行流量控制。无法检查/验证时,请使用try..catch;例如,执行IO操作


当我看到包含大量try..catch块的代码时,我的直接想法是“这是一个糟糕的设计!”。

在调用.printStackTrace或.getStackTrace之前,不会加载堆栈跟踪。
如果在catch块的开始处放置断点,您将注意到异常对象具有空堆栈跟踪

1.-调用堆栈深度不需要担心,Java实时编译器将对代码类方法内联进行优化,以在代码上提供最佳性能

2.-Catch块确实会影响性能,这是因为捕获异常可能意味着JVM中的几个不同操作,如遍历异常表、堆栈展开和常见陷阱(对JIT的优化代码进行去优化)


3.-作为一条建议,不要担心封装代码并最终导致多个方法调用,这将导致巨大的堆栈深度,JVM将进行优化,以便在编译应用程序和运行应用程序时获得最佳性能,因此始终以代码可读性和良好的设计模式为目标,因为这将有助于使代码更易于维护和修改,以防出现某些性能修复。关于catch块,请评估抛出或捕获异常的必要性,如果正在捕获,请尝试捕获最常见的异常,这样可以避免出现大量异常表。

让我们来衡量一下,好吗

package tools.bench;

import java.math.BigDecimal;

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 < 1000000000 && 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";
    }

    enum ExceptionStrategy {
        none {
            @Override void run() {
                // do nothing
            }
        },
        normal {
            @Override void run() {
                throw new RuntimeException();
            }
        },
        withoutStackTrace {
            @Override void run() {
                throw new RuntimeException() {
                    public synchronized Throwable fillInStackTrace() {
                        return this;
                    };
                };
            }
        };

        abstract void run();
    }

    private static Benchmark tryBenchmark(final int depth, final ExceptionStrategy strat) {
        return new Benchmark("try, depth = " + depth + ", " + strat) {
            @Override int run(int iterations) {
                int x = 0;
                for (int i = 1; i < iterations; i++) {
                    try {
                        x += recurseAndThrow(depth);
                    } catch (Exception e) {
                        x++;
                    }
                }
                return x;
            }

            private int recurseAndThrow(int i) {
                if (i > 0) {
                    return recurseAndThrow(i - 1) + 1;
                } else {
                    strat.run();
                    return 0;
                }
            }
        };
    }

    public static void main(String[] args) throws Exception {
        int[] depths = {1, 10, 100, 1000, 10000};
        for (int depth : depths) {
            for (ExceptionStrategy strat : ExceptionStrategy.values()) {
                System.out.println(tryBenchmark(depth, strat));
            }
        }
    }
}
显然,具体结果会因硬件、JVM实现和配置的不同而有所不同。然而,总体格局可能会保持不变

结论:

  • try语句本身产生的开销可以忽略不计
  • 抛出异常并展开调用堆栈会导致堆栈大小(或要展开的堆栈数量)的线性开销。
    • 对于实际应用程序的堆栈大小(假设为100个堆栈帧),该开销约为50微秒,即0.00005秒
    • 通过抛出没有堆栈跟踪的异常,可以在一定程度上减少这种开销
建议:

  • 不要担心try语句的性能
  • 不要对频繁发生的信号条件使用异常(例如,每秒超过1000次)
  • 否则,不要担心抛出异常的性能
  • 还有,;-)
imo与其说是性能,不如说是可读性。Java是一个非常好的Optimizing.IMHO,检索stacktrace的成本很高,但这只发生在您到达catch块的情况下。为什么还要为微观优化而烦恼呢?关注代码的可读性。如此深层次的异常是代码气味的标志。首先更改。相关线程(但不是100%重复imho):这个问题的公认答案与引发异常时调用堆栈深度如何影响性能的问题相关:如果可能,更改检查异常以避免异常。讽刺的是,这并不总是正确的。例外情况在某些情况下更快。出于语义和连贯性的原因,通常避免将它们用于控制流。这取决于。对于大多数软件来说,例外情况相对便宜。如果实现的功能对性能要求很高,则应避免出现异常(例如在游戏中),但对于循环最多发生几百次的web应用程序,抛出异常比处理错误代码和空值要好得多。您的答案是个人意见,没有硬数据支持。最后一句话确实是个人部分。但声称例外情况代价高昂,应该有数据证明这一点。根据我个人的经验,例外情况似乎没有什么影响,尽管它们应该被避免。创建异常时堆栈被“记录”,请参见
fillInStackTrace()
调用
Throwable
构造函数。但这些方法都是本机的,可能性能很高
stackTrace
为空,因为此处使用了延迟初始化。进一步您可以看到,该堆栈跟踪是通过使用其他本机方法创建的,如
getStackTraceDepth()
getStackTraceElement(int)
中的
getOurStackTrace()
try, depth = 1, none                           5.153 ns
try, depth = 1, normal                      3374.113 ns
try, depth = 1, withoutStackTrace            602.570 ns
try, depth = 10, none                         59.019 ns
try, depth = 10, normal                     9064.392 ns
try, depth = 10, withoutStackTrace          3528.987 ns
try, depth = 100, none                       604.828 ns
try, depth = 100, normal                   49387.143 ns
try, depth = 100, withoutStackTrace        27968.674 ns
try, depth = 1000, none                     5388.270 ns
try, depth = 1000, normal                 457158.668 ns
try, depth = 1000, withoutStackTrace      271881.336 ns
try, depth = 10000, none                   69793.242 ns
try, depth = 10000, normal               2895133.943 ns
try, depth = 10000, withoutStackTrace    2728533.381 ns