使用 try-catch 捕获异常会影响性能吗?
共 28348字,需浏览 57分钟
·
2023-03-11 05:06
阅读本文大概需要 12 分钟。
来自:blog.csdn.net/bokerr/article/details/122655795
前言
一、JVM 异常处理逻辑
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;
}
}
# 编译
javac TestClass.java
# 使用javap 查看 add 方法被编译后的机器指令
javap -verbose TestClass.class
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 */
个人理解,from 和 to 相当于划分区间,只要在这个区间内抛出了type 所对应的,“java/lang/Exception” 异常(主动athrow 或者 由jvm运行时检测到异常自动抛出),那么就跳转到target 所代表的第八行。
如果硬是要说的话,用了try catch 编译后指令篇幅变长了;goto 语句跳转会耗费性能,当你写个数百行代码的方法的时候,编译出来成百上千条指令,这时候这句goto的带来的影响显得微乎其微。
二、关于JVM的编译优化
前端编译与优化: 我们最常见的前端编译器是 javac,它的优化更偏向于代码结构上的优化,它主要是为了提高程序员的编码效率,不怎么关注执行效率优化;例如,数据流和控制流分析、解语法糖等等。 后端编译与优化: 后端编译包括 “即时编译[JIT]” 和 “提前编译[AOT]”,区别于前端编译器,它们最终作用体现于运行期,致力于优化从字节码生成本地机器码的过程(它们优化的是代码的执行效率)。
1. 分层编译
[客户端模式-Client、服务端模式-Server]
,它们代表的是两个不同的即时编译器,C1(Client Compiler
) 和 C2 (Server Compiler
)。解释模式下运行时,编译器不介入工作; 编译模式模式下运行,会使用即时编译器优化热点代码,有可选的即时编译器[C1 或 C2]; 混合模式为:解释模式和编译模式搭配使用。
2. 即时编译器
1. 解释模式
强制虚拟机运行于 “解释模式” -Xint 禁用后台编译 -XX:-BackgroundCompilation
2. 编译模式
# 强制虚拟机运行于 "编译模式"
-Xcomp
# 方法调用次数计数器阈值,它是基于计数器热点代码探测依据[Client模式=1500,Server模式=10000]
-XX:CompileThreshold=10
# 关闭方法调用次数热度衰减,使用方法调用计数的绝对值,它搭配上一配置项使用
-XX:-UseCounterDecay
# 除了热点方法,还有热点回边代码[循环],热点回边代码的阈值计算参考如下:
-XX:BackEdgeThreshold = 方法计数器阈值[-XX:CompileThreshold] * OSR比率[-XX:OnStackReplacePercentage]
# OSR比率默认值:Client模式=933,Server模式=140
-XX:OnStackReplacePercentag=100
3. 提前编译器:jaotc
Graal [新时代的主角]
编译器开发,因为本文用的是 C2 编译器,所以只对它做一个了解;[-XX:PrintAOT]
。三、关于测试的约束
执行用时统计
System.naoTime()
输出的是过了多少时间[微秒:10的负9次方秒]
,并不是完全精确的方法执行用时的合计,为了保证结果准确性,测试的运算次数将拉长到百万甚至千万次。编译器优化的因素
通过指令禁用 JVM 的编译优化,让它以最原始的状态运行,然后看有无 try catch 的影响。 通过指令使用即时编译,尽量做到把后端优化拉满,看看 try catch 十有会影响到 jvm的编译优化。
关于指令重排序
指令重排序发生在多线程并发场景,这么做是为了更好的利用CPU资源,在单线程测试时不需要考虑。不论如何指令重排序,都会保证最终执行结果,与单线程下的执行结果相同; 虽然我们不去测试它,但是也可以进行一些推断,参考 volatile 关键字禁止指令重排序的做法:插入内存屏障; 假定 try catch 存在屏障,导致前后的代码分割;那么最少的try catch代表最少的分割。 所以,是不是会有这样的结论呢:我们把方法体内的 多个 try catch 合并为一个 try catch 是不是反而能减少屏障呢?这么做势必造成 try catch 的范围变大。
四、测试代码
[给编译器优化预留优化的可能,这些指令可能被合并]
;public class ExecuteTryCatch {
// 100W
private static final int TIMES = 1000000;
private static final float STEP_NUM = 1f;
private static final float START_NUM = Float.MIN_VALUE;
public static void main(String[] args){
int times = 50;
ExecuteTryCatch executeTryCatch = new ExecuteTryCatch();
// 每个方法执行 50 次
while (--times >= 0){
System.out.println("times=".concat(String.valueOf(times)));
executeTryCatch.executeMillionsEveryTryWithFinally();
executeTryCatch.executeMillionsEveryTry();
executeTryCatch.executeMillionsOneTry();
executeTryCatch.executeMillionsNoneTry();
executeTryCatch.executeMillionsTestReOrder();
}
}
/**
* 千万次浮点运算不使用 try catch
* */
public void executeMillionsNoneTry(){
float num = START_NUM;
long start = System.nanoTime();
for (int i = 0; i < TIMES; ++i){
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
}
long nao = System.nanoTime() - start;
long million = nao / 1000000;
System.out.println("noneTry sum:" + num + " million:" + million + " nao: " + nao);
}
/**
* 千万次浮点运算最外层使用 try catch
* */
public void executeMillionsOneTry(){
float num = START_NUM;
long start = System.nanoTime();
try {
for (int i = 0; i < TIMES; ++i){
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
}
} catch (Exception e){
}
long nao = System.nanoTime() - start;
long million = nao / 1000000;
System.out.println("oneTry sum:" + num + " million:" + million + " nao: " + nao);
}
/**
* 千万次浮点运算循环内使用 try catch
* */
public void executeMillionsEveryTry(){
float num = START_NUM;
long start = System.nanoTime();
for (int i = 0; i < TIMES; ++i){
try {
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
} catch (Exception e) {
}
}
long nao = System.nanoTime() - start;
long million = nao / 1000000;
System.out.println("evertTry sum:" + num + " million:" + million + " nao: " + nao);
}
/**
* 千万次浮点运算循环内使用 try catch,并使用 finally
* */
public void executeMillionsEveryTryWithFinally(){
float num = START_NUM;
long start = System.nanoTime();
for (int i = 0; i < TIMES; ++i){
try {
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
} catch (Exception e) {
} finally {
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
}
}
long nao = System.nanoTime() - start;
long million = nao / 1000000;
System.out.println("finalTry sum:" + num + " million:" + million + " nao: " + nao);
}
/**
* 千万次浮点运算,循环内使用多个 try catch
* */
public void executeMillionsTestReOrder(){
float num = START_NUM;
long start = System.nanoTime();
for (int i = 0; i < TIMES; ++i){
try {
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
} catch (Exception e) { }
try {
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
} catch (Exception e){}
try {
num = num + STEP_NUM + 1f;
num = num + STEP_NUM + 2f;
} catch (Exception e) { }
try {
num = num + STEP_NUM + 3f;
num = num + STEP_NUM + 4f;
num = num + STEP_NUM + 5f;
} catch (Exception e) {}
}
long nao = System.nanoTime() - start;
long million = nao / 1000000;
System.out.println("orderTry sum:" + num + " million:" + million + " nao: " + nao);
}
}
五、解释模式下执行测试
-Xint
-XX:-BackgroundCompilation
六、编译模式测试
-Xcomp
-XX:CompileThreshold=10
-XX:-UseCounterDecay
-XX:OnStackReplacePercentage=100
-XX:InterpreterProfilePercentage=33
七、结论
URLDecoder.decode
,所以必须得捕获异常。private int getThenAddNoJudge(JSONObject json, String key){
if (Objects.isNull(json))
throw new IllegalArgumentException("参数异常");
int num;
try {
// 不校验 key 是否未空值,直接调用 toString 每次触发空指针异常并被捕获
num = 100 + Integer.parseInt(URLDecoder.decode(json.get(key).toString(), "UTF-8"));
} catch (Exception e){
num = 100;
}
return num;
}
private int getThenAddWithJudge(JSONObject json, String key){
if (Objects.isNull(json))
throw new IllegalArgumentException("参数异常");
int num;
try {
// 校验 key 是否未空值
num = 100 + Integer.parseInt(URLDecoder.decode(Objects.toString(json.get(key), "0"), "UTF-8"));
} catch (Exception e){
num = 100;
}
return num;
}
public static void main(String[] args){
int times = 1000000;// 百万次
long nao1 = System.nanoTime();
ExecuteTryCatch executeTryCatch = new ExecuteTryCatch();
for (int i = 0; i < times; i++){
executeTryCatch.getThenAddWithJudge(new JSONObject(), "anyKey");
}
long end1 = System.nanoTime();
System.out.println("未抛出异常耗时: millions=" + (end1 - nao1) / 1000000 + "毫秒 nao=" + (end1 - nao1) + "微秒");
long nao2 = System.nanoTime();
for (int i = 0; i < times; i++){
executeTryCatch.getThenAddNoJudge(new JSONObject(), "anyKey");
}
long end2 = System.nanoTime();
System.out.println("每次必抛出异常: millions=" + (end2 - nao2) / 1000000 + "毫秒 nao=" + (end2 - nao2) + "微秒");
}
推荐阅读:
互联网初中高级大厂面试题(9个G) 内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!
⬇戳阅读原文领取! 朕已阅