分析支付宝面试中关于try-catch捕获异常性能影响的常见误区及真实情况
“你看这段代码,居然在 for 循环里使用了 try-catch
,难道不知道 try-catch
会影响性能吗?”老陈一脸得意地指着屏幕里的代码:
for (int i = 0; i < 5000; i++) {
try {
dosth
} catch (Exception e) {
e.printStackTrace();
}
}
我朝屏幕靠近了一点,问道:“那老陈,你认为应该怎么改?”
“当然是把 try-catch
移到外面啊!”老陈毫不犹豫地回答。
“你这是在开玩笑吗?不谈性能,这段代码的实际目的就是要在循环内部捕获单次调用的错误,而如果放到外面,业务逻辑就会发生改变!”
老陈摸了摸他的光头,“你说得对!”
“其实,catch 整个 for 循环和在循环内部 catch 的情况下,如果没有出现异常,性能差别并不大。”我喝了口咖啡,想在老陈面前炫耀一下。
“这是什么意思?”老陈有些困惑,“try-catch
明明是有性能损耗的,我可是看过很多资料的!”
果然,老陈被我引入了讨论,我毫不犹豫地打开 IDEA,写下了以下代码进行验证:
public class TryCatchTest {
@Benchmark
public void tryfor(Blackhole blackhole) {
try {
for (int i = 0; i < 5000; i++) {
blackhole.consume(i);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Benchmark
public void fortry(Blackhole blackhole) {
for (int i = 0; i < 5000; i++) {
try {
blackhole.consume(i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
“与其争论,不如来看代码吧,老陈,我将 try-catch
从 for 循环外提到内侧,接下来我们来对比一下这两个代码的性能。”我自信满满地说道。
“哼,肯定是 tryfor 性能更好,想都不用想!”老陈信心满满地反驳。
我没有时间理会他的自信,直接启动 benchmark,结果如下:
可以发现,两者的性能(数字越大越好)相差无几:
- for-try: 86,261(100359 - 14098) ~ 114,457(100359 + 14098)
- try-for: 95,961(103216 - 7255) ~ 110,471(103216 + 7255)
我将 for 循环的次数调为 1000,结果也差不多:
老陈看着结果一脸愕然:“说好的性能影响呢?怎么没见到?”
我直接用 javap
给老陈解释,实际上这两个实现的字节码层面并没有太大区别:
try-for 的字节码
异常表记录的是 0 - 20 行,如果这些行的代码出现问题,会直接跳到 23 行处理。
for-try 的字节码
区别只在于异常表的范围更小,包裹的是 9 - 14 行,其他部分与 try-for 基本相同。
因此,从字节码的角度来看,如果没有抛出异常,两者的执行效率实际上并没有显著差异。
“那为什么网上总是传说 try-catch
会有性能问题呢?”老陈显得疑惑不解。
这个说法确实存在,在《Effective Java》这本书中提到了 try-catch
的性能问题:
并且还有接下来的描述:
正如所说,听信片面之词往往会导致错误的理解。在学习中,最怕的就是一知半解,因为完全理解能准确选择,完全不懂可能胡乱猜测,而一知半解必定选错。
《Effective Java》中强调的是不要用 try-catch
替代正常的代码,书中举例说明正常的 for 循环应当这样实现:
然而,有些人却偏偏用 try-catch
这种方式来处理循环:
这种做法有点离谱,这两种实现的性能对比确实会存在性能损耗。
我们继续对比有 try-catch
的代码和没有 try-catch
的 for 循环代码:
结果如下:
结果显示前者的性能确实稍好,这与书中提到的 try-catch
会影响 JVM 一些特定优化的说法一致,但具体影响哪些优化并未说明,我猜测可能是指令重排等问题。
综上,关于 try-catch
性能问题的总结如下:
- 相较于没有
try-catch
,确实存在一定的性能影响,但这并非建议我们完全避免使用try-catch
,而是应该合理控制其使用场景。 - 在循环内部使用
try-catch
和将try-catch
包裹整个循环的性能相差不大,但其实两者本质上是不同的业务处理方式,与性能关系不大,关键在于如何处理业务流程。 - 虽然
try-catch
可能引起性能问题,但在实际业务中不应完全避免其使用,优先考虑业务实现(当然,避免书中提到的极端情况),通常情况下性能是次要的,建议有意识地减少大范围的try-catch
,只捕获必要的部分(即使不完全捕获,也应保证代码的安全执行)。