分析支付宝面试中关于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 性能问题的总结如下

  1. 相较于没有 try-catch,确实存在一定的性能影响,但这并非建议我们完全避免使用 try-catch,而是应该合理控制其使用场景。
  2. 在循环内部使用 try-catch 和将 try-catch 包裹整个循环的性能相差不大,但其实两者本质上是不同的业务处理方式,与性能关系不大,关键在于如何处理业务流程。
  3. 虽然 try-catch 可能引起性能问题,但在实际业务中不应完全避免其使用,优先考虑业务实现(当然,避免书中提到的极端情况),通常情况下性能是次要的,建议有意识地减少大范围的 try-catch,只捕获必要的部分(即使不完全捕获,也应保证代码的安全执行)。