当前位置:网站首页>从字节码角度带你彻底理解异常中catch,return和finally,再也不用死记硬背了
从字节码角度带你彻底理解异常中catch,return和finally,再也不用死记硬背了
2022-07-30 05:15:00 【未来很长,别只看眼前】
目录
先来看两道面试题:看会不会写,等讲解完相关的字节码知识点,最后还会再来分析这两道题的;
public class Test {
public static void main(String[] args){
int result = test2();
System.out.println(result);
}
public static int test2(){
int i = 1;
try{
i++;
throw new Exception();
}catch(Exception e){
i--;
System.out.println("catch block i = "+i);
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
return i;
}
}输出结果:

然后对刚刚到例题进行改动一下,来看看输出结果会是什么?
public class Test1 {
public static void main(String[] args){
int result = test3();
System.out.println(result);
}
public static int test3(){
int i = 1;
try{
i++;
System.out.println("try block, i = "+i);
return i;
}catch(Exception e){
i ++;
System.out.println("catch block i = "+i);
return i;
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
}
}
try-catch字节码
看一段简单的代码:
public class TryCatchTest {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
}catch (Exception e) {
i = 20;
}
}
}对应字节码指令分析:

字节码指令:return 表示结束程序,ireturn才表示返回数据;
可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开(也就是检测 2~4 行)的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
第8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 2 号槽位
异常表监测的范围内发生异常后,会直接跳转到异常表中target对应的字节码指令,中间的字节码指令不再执行,比如这个案例中的goto指令就没有执行
多个catch的字节码指令分析
public static void main(String[] args) {
int i = 0;
try {
i = 10;
}catch (ArithmeticException e) {
i = 30;
}catch () {
i = 40;
}catch(Exception e){
i = 50;
}
}Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 26 //通过异常表可以知道,发生异常跳到26行
8: astore_2 //存储异常对象的引用
9: bipush 30
11: istore_1
12: goto 26 //通过异常表可以知道,发生异常跳到26行
15: astore_2 //存储异常对象的引用
16: bipush 40
18: istore_1
19: goto 26 //通过异常表可以知道,发生异常跳到26行
22: astore_2 //存储异常对象的引用
23: bipush 50
25: istore_1
26: return
Exception table:
from to target type //这里的target和上面的行号对应
2 5 8 Class java/lang/ArithmeticException
2 5 15 Class java/lang/NullPointerException
2 5 22 Class java/lang/Exception
LineNumberTable....
LocalVariableTable: //Slot 多个变量使用同一个局部变量槽是为了复用,因为这些异常同一时刻只能发生一种,所以没必要创建多个槽位来存储异常对象,可以减少开销
Start Length Slot Name Signature
9 3 2 e Ljava/lang/ArithmeticException;
16 3 2 e Ljava/lang/NullPointerException;
23 3 2 e Ljava/lang/Exception;
0 27 0 args [Ljava/lang/String;
2 25 1 i I
因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用,这样可以节省内存空间;
finally字节码分析
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}对应字节码:
从下面字节码中我们可以看到,finally的作用是把finally中的代码快复制多分,然后分别放到try代码块后,catch代码块后(goto指令前),但是有时候catch并不能完全catch你想要的exception,所以这个字节码指令会多一个保障,就是在异常表中多捕获一个异常any,和对catch多捕获一个any的异常,下面的异常表中有;
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1
//try块
2: bipush 10 //-----------try try的范围可以从异常表中查询到
4: istore_1
//try块执行完后,会执行finally,即便try中发生了异常导致try中的finally指令无法执行,但是发生异常后会跳转到catch中,catch中还是有finally中的代码指令 的
5: bipush 30 //-----------fainal 中的i = 30
7: istore_1 // 把30赋值给i,覆盖局部变量表中的1号槽位的数据
8: goto 27 //跳转到return指令,结束程序
//catch块
11: astore_2 //把异常信息放入局部变量表的2号槽位
12: bipush 20 //catch中的 i = 20代码
14: istore_1 //覆盖局部变量表中的1号槽位的数据
//catch块执行完后,会执行finally
15: bipush 30 //-----------fainal 中的i = 30
17: istore_1
18: goto 27 //跳转到return指令,结束程序
//出现异常,但未被Exception捕获,会抛出其他异常,这时也需要执行finally块中的代码
21: astore_3 //存储其他类型的异常
22: bipush 30 //-----------fainal 中的i = 30
24: istore_1
25: aload_3 //找到刚刚没有名字的异常
26: athrow //抛出这个没有名字的异常
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any //any表示的是除了你要捕获的异常之外的异常
11 15 21 any可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程注意:虽然从字节码指令看来,每个块中都有 finally 块,但是 finally 块中的代码只会被执行一次;
finally 中带return
先看一段代码:
public class FinallyReturnTest {
public static void main(String[] args) {
int i = FinallyReturnTest.test();
// 结果为 20
System.out.println(i);
}
public static int test() {
int i;
try {
i = 10;
return i;
} finally {
i = 20;
return i;
}
}
}对应字节码文件分析:
Code:
stack=1, locals=3, args_size=0
0: bipush 10 //放入栈顶
2: istore_0 //slot 0 (从栈顶移除了,把该值存储到局部变量表中了)
3: iload_0 //从局部变量表中把0号槽位的数据加载到栈中
4: istore_1 //注意:暂存返回值,又把这个10存储到局部变量表中的【1号】槽位 备份使用
5: bipush 20 //------finally中代码块
7: istore_0 //20这个值对0号槽位的10进行了覆盖
8: iload_0 //把0号槽位的值加载到栈中
9: ireturn // ireturn 会【返回操作数栈顶】的整型值 20,返回的数据是操作数栈中的数据
// 如果出现异常,还是会执行finally 块中的内容,没有抛出异常
10: astore_2 // 存储异常,从异常表中得出该行指令是存储移除用的
11: bipush 20 //------finally中代码块
13: istore_0 //20这个值对0号槽位的10进行了覆盖
14: iload_0 //把0号槽位的值加载到栈中
15: ireturn //注意:这里没有 athrow 了,也就是如果在 finally 块中如果有返回操作的话,那么try中代码块出现异常,会吞掉异常!并不会抛出异常
Exception table:
from to target type
0 5 10 any
由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以finally的为准
至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子(有大用)
跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常,如果try中也有return那么try中的return和finally中的return最终在字节码层面只会执行一条return指令
所以不要在finally中进行返回操作
public static int test() {
int i;
try {
i = 10;
// 这里应该会抛出异常
i = i/0;
return i;
} finally {
i = 20;
return i;
}
}会发现打印结果为 20 ,而且并未抛出异常;

把finally中的return给去掉
但是如果我们把finally中的return给去掉,那么返回的又是什么?
public static int test() {
int i = 10;
try {
return i;
} finally {
i = 20; //最后的结果是返回10 !!!
}
}对应的字节码:
Code:
stack=1, locals=3, args_size=0
0: bipush 10 //把10放入栈顶
2: istore_0 // 把10存储在局部变量表的0号槽位
3: iload_0 // 然后从局部变量表中把10又加载到操作数栈顶,按理说此时该返回了,但是明显没有立马返回,而是istore_1,把刚刚加载到操作数栈中的10又在局部变量表中的1号槽位备份一份
4: istore_1 // 加载到局部变量表的1号位置,【目的是为了固定返回值】
5: bipush 20 //------执行finally代码块
7: istore_0 // 把20赋值给i
8: iload_1 // 【加载局部变量表1号位置的数10到操作数栈】
9: ireturn // 返回操作数栈顶元素 10
10: astore_2 //存储异常对象
11: bipush 20 //------执行finally代码块
13: istore_0 //把20赋值给i
14: aload_2 // 加载异常
15: athrow // 仍然会抛出异常
Exception table:
from to target type
3 5 10 any
在把finally中的return去掉后,我们发现如果在try中进行了return, 如果没有发生异常的话,那么即便finally中的变量发生了变化,那么try中返回的依旧是try中的变量值,因为我们可以从字节码指令看到try中的变量会先被备份一次用来返回;
如果发生了异常那么返回的值就是catch中的变量:比如下面的案例;

字节码分析:

开始两个例题的分析
这里就不再使用字节码码来分析了,前面已经把try catch finally对应的字节码都分析了一遍,所以这里就不再使用字节码来分析了;
public class Test {
public static void main(String[] args){
int result = test2();
System.out.println(result);
}
public static int test2(){
int i = 1;
try{
i++; //i从自增变成2
throw new Exception(); //抛出异常,被catch捕获
}catch(Exception e){
i--; //对i进行自减操作 变为1
System.out.println("catch block i = "+i); //执行这行代码 此时i为1
//finally中的代码被拷贝到这里来执行 【i = 10 把之前的i进行覆盖 ,此时局部变量表中存储的i为10】
//输出System.out.println("finally block i = "+10);
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
return i; //返回i,先从局部变量表中加载对应的变量,然后进行弹栈
}
}所以输出的是:
catch block i = 1
finally block i = 10
10第二题:
public class Test1 {
public static void main(String[] args){
int result = test3();
System.out.println(result);
}
public static int test3(){
int i = 1;
try{
i++; //i从自增变成2,然后存储到局部变量表中
System.out.println("try block, i = "+i); //输出
//因为没有发生异常所以不会跳转到catch中,但是finally中的代码会被拷贝在return之前来执行
//因为finally中没有return,所以try和catch中的变量都会自己额外拷贝一份,用来最后作为返回值返回
return i;
}catch(Exception e){
i ++;
System.out.println("catch block i = "+i);
return i;
}finally{
i = 10;
System.out.println("finally block i = "+i);
}
}
}输出结果:
try block, i = 2
finally block i = 10
2字节码片段分析:如果finally中没有return,那么try 和catch中都是会对直接到 i 进行备份用于返回的,即便finally中的代码改变了其值,但是最后返回的值还是以try和catch中的值为准,因为JVM返回的是备份的值;


边栏推荐
猜你喜欢

美国再次加息75个基点 陷入“技术性衰退”?加密市场却呈现复苏力量

涂鸦Wi-Fi&BLE SoC开发幻彩灯带

ThinkPHP high imitation blue play cloud network disk system source code / docking easy payment system program

从想当亿万富翁到职场、创业、爱情、抑郁、学医学武,我的程序人生
![[High Performance Computing] openMP](/img/a5/2cfd760a26edb379d337eb3d1605d5.jpg)
[High Performance Computing] openMP

Hexagon_V65_Programmers_Reference_Manual (14)

el-table中加入el-input框和el-input-number框,实现el-table的可编辑功能

一文带你吃透js处理树状结构数据的增删改查

Small programs use npm packages to customize global styles

How MySQL to prepare SQL pretreatment (solve the query IN SQL pretreatment can only query out the problem of a record)
随机推荐
Hexagon_V65_Programmers_Reference_Manual(14)
Hexagon_V65_Programmers_Reference_Manual(11)
【Vitis】ZCU102开发板PS端控制PL端复位的代码实现
go语言学习笔记三
C language implements highly secure game archives and reads files
mysql basics (4)
go版本升级
Recursive Optimization of Fibonacci Sequences "Memo Recursion"
翻译 | 解读首部 Kubernetes 纪录片
2022鹏城杯web
容器化|在 S3 备份恢复 RadonDB MySQL 集群数据
容器化 | 构建 RadonDB MySQL 集群监控平台
黄金圈法则:成功者必备的深度思考方法
一文带你吃透js处理树状结构数据的增删改查
MySQL基础(DDL、DML、DQL)
Us to raise interest rates by 75 basis points in "technical recession"?Encryption market is recovering
CSDN Meetup 回顾 丨从数据湖到指标中台,提升数据分析 ROI
LeetCode Algorithm 328. 奇偶链表
Kyligence 再获 CRN, insideBIGDATA 两大国际奖项认可
程序员大保健指南,给自己的身心偶尔放松的机会