在 Java 中,在实际上没有错误的情况下使用 throw/catch 作为逻辑的一部分通常是一个坏主意(部分)因为抛出和捕获异常是昂贵的,并且在循环中多次执行通常很远比其他不涉及抛出异常的控制结构慢。
我的问题是,是在 throw/catch 本身产生的成本,还是在创建 Exception 对象时产生的成本(因为它获得了很多运行时信息,包括执行堆栈)?
换句话说,如果我这样做了
Exception e = new Exception();
但不要扔,这是扔的大部分成本,还是扔 + 接处理的成本高?
我不是在问将代码放在 try/catch block 中是否会增加执行该代码的成本,我是在问捕获异常是否是昂贵的部分,或者创建(调用构造函数)异常是昂贵的部分。
问这个问题的另一种方式是,如果我创建一个 Exception 实例并一遍又一遍地抛出和捕获它,这会比每次抛出时创建一个新的 Exception 快得多吗?
创建异常对象并不必然比创建其他常规对象更昂贵。主要成本隐藏在原生fillInStackTrace
遍历调用堆栈并收集构建堆栈跟踪所需的所有信息的方法:类、方法名称、行号等。
大多数 Throwable
构造函数都会隐式调用 fillInStackTrace
。这就是创建异常缓慢的想法的来源。但是,有一个 constructor创建一个没有堆栈跟踪的 Throwable
。它允许您制作可快速实例化的 throwable。创建轻量级异常的另一种方法是覆盖 fillInStackTrace
。
现在抛出异常怎么办?
事实上,这取决于抛出的异常在哪里捕获。
如果它在同一个方法中被捕获(或者更准确地说,在同一个上下文中,因为上下文可以包含多个方法由于内联),那么 throw
就像 goto
(当然是在JIT编译之后)。
但是,如果 catch
block 位于堆栈的更深处,则 JVM 需要展开堆栈帧,这可能需要更长的时间。如果涉及到 synchronized
block 或方法,则需要更长的时间,因为展开意味着释放已删除的堆栈帧所拥有的监视器。
我可以通过适当的基准来确认上述陈述,但幸运的是我不需要这样做,因为 HotSpot 的性能工程师 Alexey Shipilev 的帖子中已经完美涵盖了所有方面:The Exceptional Performance of Lil' Exception .
我是一名优秀的程序员,十分优秀!