一、try-catch
说明:在出现try-catch代码块的情况,会先进入try代码块,然后监控整个try中的代码,如果出现异常就会catch代码块中。
实例代码:
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
}
对应字节码:
- 首先是第0-1行,对应的就是int i= 0 ,将0存入本地变量1号槽位
- 2-5行对应着cry中的代码,先将10压入操作数栈,然后将操作数栈中的10存放到本地变量1号槽位,如果期间没有出现异常就执行第5行的goto,找到第12行return结束 这时候看红框框出的部分,表示监控着2-5行,不包括第5行,如果期间出现异常,就会匹配异常,如果和我们catch中的异常一致那么就不走第5行,而是接着进入第8行
- 8-11行表示进入catch块中,第八行表示将Exception放入本地变量2号槽位,9-11表示将20赋值给1本地变量1号槽位i
- 12行return结束
二、多个single-catch块的情况
说明:会先进入try代码块中执行,如果在try中出现了异常,这时候会对异常类型进行判断,根据正确或者说更精细的异常匹配到相应的catch块入口
实例代码:
public class Demo3_11_2 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (ArithmeticException e) {
i = 30;
} catch (NullPointerException e) {
i = 40;
} catch (Exception e) {
i = 50;
}
}
}
对应字节码:
- 首先是第0-1行,对应的就是int i= 0 ,将0存入本地变量1号槽位
- 2-5行对应着cry中的代码,先将10压入操作数栈,然后将操作数栈中的10存放到本地变量1号槽位,如果期间没有出现异常就执行第5行的goto,找到第12行return结束
- 如果期间出现了异常,就会根据第一个红框中的异常监控,找到匹配的异常,然后根据target找到下一行的入口,例如,如果是NullPointerException异常,那么就会跳到15行
- 15-19行表示出现NullPointerException后执行对应的catch块代码,当执行完后就会执行第19行goto,然后找到26行return结束
- 在整个过程中用于存储异常的本地变量槽位只有一个二号槽位,使用的是槽位复用,减少不必要的开销
try-catch-finally
说明:会先进入try代码块,如果期间出现异常就会进入catch块,当catch块执行完后进入finally块。如果期间没有出现异常,执行完try块之后就会进入finally块。也就是finally是最终必执行的代码块
实例代码:
public class Demo3_11_4 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}
对应字节码
- 首先是第0-1行,对应的就是int i= 0 ,将0存入本地变量1号槽位
- 2-4行对应着cry中的代码,先将10压入操作数栈,然后将操作数栈中的10存放到本地变量1号槽位如果期间没有出现问题那么就会接着执行5-8行,这几行实际是finaly中的指令 -如果期间出现了异常,先来看一下4号红框中的内容,第三行中的 2 5 11 Class java/lang/Exception 会监控2-5行号的代码,不包括第五行,也就是try块中的指令,如果发生了异常时Exception类型的 那么就会进入第11行
- 11-14行就是catch块中的内容,将30赋值给i,如果期间没有出现问题,那么就会接着执行15-18行,这几行实际时finally中的指令 如果期间出现了异常,再来看一下4号红框中的内容,第四行2 5 21 any 代表的意思时,监控2-5行号的代码,不包括第五行,也就是try块中的指令,如果发生了异常,但是异常的类型并不是我们指定catch中的接收的异常类型,那么就会进入21行号的代码,也就是finally块,第五行11 15 21 any 代表的是监控着11-15行的代码,不包括15行,也就是监控catch块中的代码,如果再次出现了异常,那么就进入21行,也就是finally块中的指令
- 21-26中就表示try发生异常但是catch中无法接收异常或者catch中发生异常的情况,将发生的异常放入本地变量表3号槽位,然后执行finally中的指令,26行将异常抛出,执行完毕后就return结束
- 也就是说虚拟机在编译的时候会将finally中的内容复制三分,一份放在try的末尾,一份放在catch的末尾,一份放在finally的位置,并且如果try发生了异常,在catch中进行处理,可以不抛出异常,但是再finally中进行处理,一定会抛出异常,除非finally中有return
try-finally try中有return,finally中没有return
说明:会先执行try中的指令,当执行到return前,会先将finaly中的指令执行完毕后再执行try中的return
实例代码:
public class Demo3_12_2 {
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
int i = 10;
try {
return i;
} finally {
i = 20;
}
}
}
对应字节码:
- 0-2行中执行的是try之前的代码 ,将10压入栈顶后存入本地变量表0号槽位
- 3-4行在return i 之前,因为还有finally的代码块没执行,先将本地变量表0号槽位的值压入栈顶中,然后又将值保留到本地变量表1号槽位,为了保证返回值就是10。
- 而5-7行则执行的是finally的内容,先将20压入栈顶,然后存入本地变量表0号槽位,也就是i
- 8-9行 当finally的内容执行完了之后,就会将try块return之前的i的值从本地变量表1号槽位中取出来,压入栈顶,然后返回。
- 红框的内容表示,会监控3-5行的指令,不包括第五行,如果期间出现异常那么就会进入第10行号指令,也就是finally块的内容
- 10-15 整个finally要做的事情
- 第10行表示将异常类型存入本地变量表的2号槽位
- 11-13行 用户显示的在finally块中输入的内容,将20压入栈顶,然后保存到本地变量表0号槽位
- 14行将本地变量表2号槽位载入操作数栈中,也就是异常
- 15行抛出异常
try-finally try中有return finally中也有return
说明:会先执行try中的指令,当执行到return 的时候,会跳过return,继续执行finally中的内容
实例代码:
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
try {
return 10;
} finally {
return 20;
}
}
对应字节码:
0行指令将10压入栈顶,在第二行中又将10从栈顶移除,然后执行finaly中的内容,先将20压入栈顶,然后返回栈顶内容。整个过程中监控0-3行,不包括第3行号,如果出现了异常就会直接进入第六行,将异常类型存入本地变量表1号槽位,然后开始执行finally的代码,值得注意的是当finally中有返回值的时候,会吞掉异常,直接返回。这样我们根本不知道程序出现了异常