我们知道在Java中异常处理有两种方式,一种是try...catch...一下,这个在上一次【https://www.cnblogs.com/webor2006/p/9706466.html】中已经进行了详细且完整的分析,但是还缺少往方法上throws的情况,所以这里对其进行补充一下,先修改源代码:
当然啦这个throws没啥意义,因为我们在代码块中捕获了,但这里只是为了说明问题,然后编译再用jclasslib查看一下:
其中也能理解Code指的是方法体中的相关的字节信息,如下:
那点开“Exceptions”查看一下内容:
那如果我们抛出一个运行期的异常,比如NullPointException ,那还会在这个“Exceptions”中呈现么,试试:
再次编译查看字节:
也是会反馈的,所以需要明确的是:在方法全中try...catch..和throws异常在字节码中是不一样的。关于异常这块的东东就学到这了,接下来则会进入另一个新的东东。
栈帧与操作数栈剖析:
先来输出一下理论:
栈帧【stack frame】:是用于帮助虚拟机执行方法调用与方法执行的数据结构。本身是一种数据结构,封装了方法的局部变量表【之前已经学习过了】、动态链接信息【像c++在编译期就确定了类与类之间的关系,而java则不一样,在编译期间是不知道类之间的关系的,只有在类加载或真正调用时才能知道类之间的关第,基于此就出现了符号引用与直接引用,也就是下面会学到的】、方法的返回地址【当一个方法调用目标方法时,最终目标方法会返回方法调用方的位置处,所以需要记录下来方法返回的地址信息】以及操作数栈【之前已经学习过了】等信息。
下面用一个形象的图来描述一下,其实对于方法调用都是以栈帧的形式存在于栈中:
而对于方法代码的执行就存在出栈与入栈两个操作,JVM对于操作都是基于栈的,比如说两个数的减法:3-2 = 1,对于这个操作JVM是如何完成的呢,下面来画一下:
先把3压入到栈中:
然后再把2也压入栈:
然后需要对这两个数进行减法操作,所以会有相关的减法指令,当看到该减法指令之后,因为会涉及到两个操作数,所以会从栈中弹出两个数,首先将2弹出:
此时栈中就只有3了:
然后再把3弹出栈:
此时栈为空栈了:
最后将计算的结果1压入到栈中:
另外再来说一下局部变量,存储局部变量的最小单位称为slot,比如说有10个局部变量则会有10个slot来进行存储,但是这也不是绝对的,因为数据类型的长度是不一样的,像比较短的short、int等类型的长度较短,一个slot就可以存储下,那对于long、double占据长度比较多的类型就会用两个连续的slot来表示,当然啦对于要多个slot来表示的一个局部变量要读的话也是读连续的几个slot,不然就数据不对了;另外对于10个局部变量不是一定得要10个slot来存储的,因为slot是可以复用的,我们知道方法体中的局部变量出了方法作用域就会回收,但是方法体里面又是可以N多个更小的作用域的,怎么理解,写一个伪代码:
而对于方法局部变量表中是不会区分是否是更小的作用域的,都当一个局部变量对待,而如果像上面b和c出了作用域生命周期结束之后,那么这两个局部变量的slot有可能会给下面的d和e重复使用,当然啦也有可能不复用,具体得看JVM的具体实现,总之需要明确的是:对于10个局部变量,有可能需要10个slot,也有可能不需要这么多。
符号引用与直接引用的转换:
关于符号引用和直接引用在学JVM开篇就提出过,咱们再来回忆一下:
有些符号引用是在类加载阶段或是第一次使用时就会转换成直接引用,这种转换叫做静态解析;另外一些符号引用则是在每次运行期转换为直接引用,这种转换叫做动态链接,这体现为Java的多态性。比如说下面的多态代码:
非常要注意的是:在编译期间字节码看到的a.sleep()全是Animal的,涉及到invokevirsual指令,而该指令主要是动态派发的指令,它会检查a真正指向那个对象在运行期间会动态的转换成真正指向那个对象的sleep()了,所以对于这段代码有3个a.sleep(),则会进行三次的转换,关于invokevirsual指令在之后会详细进行说明,目前先有个理论概念在于脑海既可。