深入java虚拟机 异常,异常表, finally

每个异常表入口包含四个信息:

 
深入java虚拟机 异常,异常表, finally_第1张图片
 

 

下面一个小例子:

  

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方法的 字节码:

 
深入java虚拟机 异常,异常表, finally_第2张图片
 

testException方法的异常表:
深入java虚拟机 异常,异常表, finally_第3张图片

 

有上面的异常表可以知道,异常表中包含三个异常: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,所以只展示这些常量池入口:

 
深入java虚拟机 异常,异常表, finally_第4张图片
 

 

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方法的字节码为:

  
深入java虚拟机 异常,异常表, finally_第5张图片
 
深入java虚拟机 异常,异常表, finally_第6张图片
 

   该方法的异常表为:

  
深入java虚拟机 异常,异常表, finally_第7张图片
 

 由上面的bytecode和异常表可以知道:

 1 上面的bytecode存在重复的部分, #12至#17, #31至#36, #47至#52, #59至#64 这四个区域是重复的部分;他们是代码块finally语句块编译的部分

 2 根据异常表可以知道每个异常捕捉到以后执行的行数;结合上面的bytecode可以知道,每个异常的bytecode执行之后就是finally语句块的bytecode;正常结束以后也是finally语句块的bytecode

 3 结合以上两点可知,finally语句的字节码在任何可能执行到它的地方都加上了(正常结束,三个异常后面,都跟有finally语句的字节码)

你可能感兴趣的:(jvm,异常,finally,深入jvm,异常表)