不知道从何时起,传出了这么一句话:Java中使用try catch 会严重影响性能。然而,事实真的如此么?我们对try catch 应该畏之如猛虎么?
Java 程序中显式抛出异常由athrow指令支持,除了通过 throw 主动抛出异常外,JVM规范中还规定了许多运行时异常会在检测到异常状况时自动抛出(效果等同athrow), 例如除数为0时就会自动抛出异常,以及大名鼎鼎的 NullPointerException 。
还需要注意的是,JVM 中 异常处理的catch语句不再由字节码指令来实现(很早之前通过 jsr和 ret指令来完成,它们在很早之前的版本里就被舍弃了),现在的JVM通过异常表(Exception table 方法体中能找到其内容)来完成 catch 语句;很多人说try catch 影响性能可能就是因为认识还停留于上古时代。
我们编写如下的类,add 方法中计算 ++x; 并捕获异常。
- public class TestClass {
- private static int len = 779;
- public int add(int x){
- try {
- // 若运行时检测到 x = 0,那么 jvm会自动抛出异常,(可以理解成由jvm自己负责 athrow 指令调用)
- x = 100/x;
- } catch (Exception e) {
- x = 100;
- }
- return x;
- }
- }
使用javap 工具查看上述类的编译后的class文件
- # 编译
- javac TestClass.java
- # 使用javap 查看 add 方法被编译后的机器指令
- javap -verbose TestClass.class
忽略常量池等其他信息,下边贴出add 方法编译后的 机器指令集:
- public int add(int);
- descriptor: (I)I
- flags: ACC_PUBLIC
- Code:
- stack=2, locals=3, args_size=2
- 0: bipush 100 // 加载参数100
- 2: iload_1 // 将一个int型变量推至栈顶
- 3: idiv // 相除
- 4: istore_1 // 除的结果值压入本地变量
- 5: goto 11 // 跳转到指令:11
- 8: astore_2 // 将引用类型值压入本地变量
- 9: bipush 100 // 将单字节常量推送栈顶<这里与数值100有关,可以尝试修改100后的编译结果:iconst、bipush、ldc>
- 10: istore_1 // 将int类型值压入本地变量
- 11: iload_1 // int 型变量推栈顶
- 12: ireturn // 返回
- // 注意看 from 和 to 以及 targer,然后对照着去看上述指令
- Exception table:
- from to target type
- 0 5 8 Class java/lang/Exception
- LineNumberTable:
- line 6: 0
- line 9: 5
- line 7: 8
- line 8: 9
- line 10: 11
- StackMapTable: number_of_entries = 2
- frame_type = 72 /* same_locals_1_stack_item */
- stack = [ class java/lang/Exception ]
- frame_type = 2 /* same */
-
再来看 Exception table:

from=0, to=5。指令 0~5 对应的就是 try 语句包含的内容,而targer = 8 正好对应 catch 语句块内部操作。
个人理解,from 和 to 相当于划分区间,只要在这个区间内抛出了type 所对应的,“java/lang/Exception” 异常(主动athrow 或者 由jvm运行时检测到异常自动抛出),那么就跳转到target 所代表的第八行。
若执行过程中,没有异常,直接从第5条指令跳转到第11条指令后返回,由此可见未发生异常时,所谓的性能损耗几乎不存在;
如果硬是要说的话,用了try catch 编译后指令篇幅变长了;goto 语句跳转会耗费性能,当你写个数百行代码的方法的时候,编译出来成百上千条指令,这时候这句goto的带来的影响显得微乎其微。如图所示为去掉try catch 后的指令篇幅,几乎等同上述指令的前五条。

综上所述:“Java中使用try catch 会严重影响性能” 是民间说法,它并不成立。如果不信,接着看下面的测试吧。
其实写出测试用例并不是很难,这里我们需要重点考虑的是编译器的自动优化,是否会因此得到不同的测试结果?
本节会粗略的介绍一些jvm编译器相关的概念,讲它只为更精确的测试结果,通过它我们可以窥探 try catch 是否会影响JVM的编译优化。
前端编译与优化:我们最常见的前端编译器是 javac,它的优化更偏向于代码结构上的优化,它主要是为了提高程序员的编码效率,不怎么关注执行效率优化;例如,数据流和控制流分析、解语法糖等等。
后端编译与优化:后端编译包括 “即时编译[JIT]” 和 “提前编译[AOT]”,区别于前端编译器,它们最终作用体现于运行期,致力于优化从字节码生成本地机器码的过程(它们优化的是代码的执行效率)。