每个异常表入口包含四个信息:
下面一个小例子:
public class GreetDemo {
public static void main (String[] args) {
GreetDemo gd = new GreetDemo();
gd.testException();
}
public void testException () {
try {
System.out.println("hi");
int m = 1/0;
} catch (NullPointerException e) {
throw e;
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
testException方法的 字节码:
testException方法的异常表:
有上面的异常表可以知道,异常表中包含三个异常:NullPointerException , ArithmeticException, Exception
NullPointerException 捕捉的范围为上面bytecode的#0至#12 处理该异常从#15开始
ArithmeticException 捕捉的范围为上面bytecode的#0至#12 处理该异常从#18开始
ArithmeticException 捕捉的范围为上面bytecode的#0至#12 处理该异常从#26开始
分析一下上面bytecode
首先看一下常量池
由于上面的bytecode中只出现了常量池入口5,6,7,10,12,所以只展示这些常量池入口:
getstatic #5 <java/lang/System/out Ljava/io/PrintStream;> //获取静态变量
ldc #6 <hi> //从常量池获取常量"hi"并压入栈
invokevirtual #7 <java/io/PrintStream/println(Ljava/lang/String;)V> //调用动态方法
iconst_1 //把int类型的常量1压入栈
iconst_0 //把int类型的常量1压入栈
idiv //对栈顶的两个int类型进行处罚
istore_1 //从栈顶弹出int类型的值,并赋值给位置为1的局部变量
goto 19 //跳转的方法末,return, 退出此方法(方法正常结束)
astore_1 //进入catch分支,把栈顶对象引用弹出,赋值给位置为1局部变量
aload_1 //把位置为1的对象引用进栈
athrow //弹出栈中异常引用,抛出异常
astore_1 //进入catch分支,把栈顶对象引用弹出,赋值给位置为1局部变量
aload_1 //把位置为1的对象引用进栈
invokevirtual #10 <java/lang/ArithmeticException/printStackTrace()V> //调用异常的printStackTrace方法
goto 19 //跳转的方法末,return, 退出此方法
astore_1
aload_1
invokevirtual #12 <java/lang/Exception/printStackTrace()V>
return //退出方法
每个异常方法都和异常表相关联, 该异常表与方法的字节码序列一起送到class文件中
如果在方法执行的时候抛出异常,java虚拟机将会在整个异常表中搜索相匹配的项, 如果当前程序计数器在异常表的入口所指定的范围内, 而且所抛出的异常是该入口所指向的类(或为指定类的之类),那么该入口即为所询入口,当遇到第一个匹配项时, 虚拟机将程序计数器设为新的pc指针偏移量位置, 然后从该位置执行, 如果没有发现匹配项, 虚拟机从当前栈中弹出, 再次抛出同样的异常;当虚拟机弹出当前栈桢时,虚拟机马上停止执行当前方法,并且返回至调用本方法的方法, 但是并不继续执行该方法, 而是再该方法抛出同样的异常, 这就是虚拟机在该方法中执行同样的搜索异常表的操作
finally 关键字:
finally的执行大家都知道,在字节码中如何反应的呢?
下面是一个小例子:
public void testException () {
try {
System.out.println("hi");
int m = 1/0;
} catch (NullPointerException e) {
throw e;
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("fuck you");
}
System.out.println("after fuck you, i want say hello");
}
testException方法的字节码为:
该方法的异常表为:
由上面的bytecode和异常表可以知道:
1 上面的bytecode存在重复的部分, #12至#17, #31至#36, #47至#52, #59至#64 这四个区域是重复的部分;他们是代码块finally语句块编译的部分
2 根据异常表可以知道每个异常捕捉到以后执行的行数;结合上面的bytecode可以知道,每个异常的bytecode执行之后就是finally语句块的bytecode;正常结束以后也是finally语句块的bytecode
3 结合以上两点可知,finally语句的字节码在任何可能执行到它的地方都加上了(正常结束,三个异常后面,都跟有finally语句的字节码)