本文是《深入理解Java虚拟机(第二版)》第八章的笔记
运行时栈帧结构
在活动线程中,只有栈顶的栈时有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。下面对栈帧的4个主要部分进行分析。
局部变量表
存放方法参数和方法内部定义的局部变量
一些细节:
- 最小存储单元(Slot),一个Slot可以存32位以内的数据类型。
boolean、byte、char、short、int、float、reference、returnAddress
占一个Slot;long、double
占两个Slot,是非原子的,但它是线程安全的,因为它是栈中的,是线程私有的。- 对于实例方法,第一个Slot是传递所属对象实例的引用,也就是我们常用的
this
。 - 注意局部变量没有初始值哦。
- 当一个变量的pc寄存器的值大于Slot的作用域时,Slot是可以复用的。
操作数栈
- 虚拟机字节码执行引擎是“基于栈的执行引擎”,这个栈就是操作数栈。
- 对比基于寄存器的执行引擎
优点:可移植性、代码更加紧凑、编译器实现更紧凑。确定就是速度更慢。
- 下面栈帧的部分操作数栈与上面的栈帧的部分局部变量是重叠的。
动态连接
指向运行时常量池中该栈帧所属方法的引用,这个引用的为了支持方法调用过程的动态连接。具体内容在下面的方法调用中解释。
方法返回地址
方法退出(也就是当前栈帧出栈)的两种方式:
return
正常退出- 异常退出,根据异常表得返回出口
方法调用
方法调用不等同于方法的执行,方法调用阶段唯一的任务就是确定被调用方法的版本。说白了就是找方法,方法唯一就直接确定(解析)。方法不唯一:重载(静态分配)、重写(动态分配)
解析调用
-
调用目标在程序代码写好、编译器进行编译时就确定好的。这类方法时调用称为解析。是静态的。
-
非虚方法:静态方法、私有方法、实例构造器、父类方法、final方法。它们都是采用解析调用。反之其它就是虚方法。
分派调用
Human man = new Man();
Human
是静态类型(外观类型),Man
是实际类型
静态分派
依赖静态类型来定位方法执行的版本的分配动作称为静态分配。最典型的应用是方法重载。
- 编译器在重载时是根据参数的静态类型作为依据的。
- 由于字面量没有显式的静态类型,它重载时可能会有多种选择,只是选一个更好的版本。
- 静态分配和解析不是互斥的,例如静态方法也是可以重载的。
- 注意:静态分配更严格,一定是要用静态类型做参数。
动态分派
依赖实际类型来定位方法执行的版本的分配动作称为动态分配。最典型的应用是方法重写。
- 虚拟器在重写时是对象的实际类型作为依据的。
- 注意:动态分配更宽松,如果实际类型中没有对应的方法,就会向上找父类里的相同方法来调用。
一个测试例子:
public class MixTest { |