深入java虚拟机 错误和错误表
深入java虚拟机 异常和异常表
每个异常表入口包含四个信息:
下面一个小例子:
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指针偏移量位置, 然后从该位置执行, 如果没有发现匹配项, 虚拟机从当前栈中弹出, 再次抛出同样的异常;当虚拟机弹出当前栈桢时,虚拟机马上停止执行当前方法,并且返回至调用本方法的方法, 但是并不继续执行该方法, 而是再该方法抛出同样的异常, 这就是虚拟机在该方法中执行同样的搜索异常表的操作