虚拟机栈
虚拟机栈是一个线程独有的,每一个虚拟机栈中是由一个个栈帧组成的。一个个的栈帧就是当前线程执行的方法。
一个栈帧由局部变量表,操作数栈,动态链接,方法返回地址组成。
局部变量表
局部变量表存放着一个方法里面所有的局部变量,局部变量不会向类变量一样在类加载的过程中进行初始化,所以声明局部变量的时候就要初始化。
局部变量表的大小在编译时期就已经确定好了。局部变量表的基本单位是slot(槽)。32位以内的类型占一个slot,64位的类型(long, double)占两个slot
局部变量表中的变量也会是垃圾回收的根节点,被局部变量表引用或者间接引用的对象是不会被垃圾回收的。
操作数栈
2/12 谁说站在光里的才是英雄
操作数栈是临时保存变量的地方,根据字节码指令将操作数出栈或者入栈。
这个方法中PC,局部变量表,操作数栈变化如下:
![image-2022021209462790](https://gitee.com/iandf/pics/raw/master/img/image-20220212094627906.png)
栈顶缓存技术:所有操作数栈的内容都会缓存到寄存器中,也就是15会缓存到寄存器中,istore_1指令将栈顶的数存到局部变量表的时候,不用去内存中读取栈的数据,而是直接从寄存器中取,加快执行速度
![image-20220212094705206](https://gitee.com/iandf/pics/raw/master/img/image-20220212094705206.png)
![image-20220212094722769](https://gitee.com/iandf/pics/raw/master/img/image-20220212094722769.png)
![image-20220212094738919](https://gitee.com/iandf/pics/raw/master/img/image-20220212094738919.png)
动态链接
动态链接的作用就是把字节码指令中的变量和方法的符号引用转换成常连池中的直接引用。
如下图所示
![image-20220212105720445](https://gitee.com/iandf/pics/raw/master/img/image-20220212105720445.png)
静态绑定和动态绑定
静态绑定和动态绑定 对应的就是 非虚方法和虚方法。
非虚方法指的就是在编译器能够确定下来的方法 一般的字节码指令是invokestatic 和 invokespecial
- invokestatic : 指的是静态方法
- invokespecial: 指的是构造器,私有方法,final方法
final方法虽然是非虚方法,但是他的字节码指令是 invokevirtual
虚方法一般是指的是运行时,才能确定是哪个方法。一般的字节码指令是invokevirtual 和 invokeinterface
- invokevirtual :比如非私有,静态,final的类方法
- invokeinterface: 接口中的方法
静态类型语言与动态类型语言
静态类型语言和动态类型语言的区别就在于在对类型的检查是在编译期间还是运行期间,前者是静态类型语言,后者是动态类型语言
java 是静态类型语言,但是java虚拟机可以运行很多的语言,为了支持 动态类型语言 java7引入了一个 invokedynamic 指令。
在java8的 Lambda 表达式中可以看到字节码用的就是这条指令
方法重写的本质和虚方法表
方法重写的本质
- 设操作数栈的栈顶元素类型为C,字节码指令指定要调用一个方法
- 如果在类型C中找到权限与常量中描述符相符则进行权限访问,权限访问通过就返回方法的直接引用地址,如果不通过则返回java.lang.IllegalAccessError
- 如果没找到则一层一层从下往上往他的父类按照2的步骤去找
- 如果始终没有找到则会爆出java.lang.AbstractMethodError
出现这种异常的通常是maven冲入啥的,导致方法找不到或者权限不通过
那么每一次我们去执行方法还要一层一层去找,会很麻烦并且影响效率。为了解决这个问题引入了虚方法表。
虚方法不是编译期间能够确定下来的,所以他在类加载的链接的第三步解析的时候确定下来,并建立虚方法表确定这个方法是属于哪个类的,这样调用的时候直接返回这个方法的地址即可。
例如以下例子
CockerSpaniel 的虚方法表为
有了这个虚方法表就可以直接看这个方法在那个类里面,然后进行权限的访问。不用一层一层的往上去找浪费时间。