1. 栈帧
a) 是虚拟机栈的栈元素。
b) 每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
c) 栈帧需要的内存大小在编译时已经确定,不会受运行时的影响。
d) 栈帧结构
i. 局部变量表
1. 用于存放方法参数和方法内部定义的局部变量。
2. 以slot为单位。
3. 建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的slot是否为原子操作,都不会引起数据安全问题。
4. 第0位的slot默认用于传递方法所属对象实例的引用。
5. 重点内容(为我解答了这个问题:为什么一个对象的引用被赋值null后也不会被gc掉?赋null值得操作在经过JIT编译优化后就会被消除掉,要在极特殊情形(对象占用内存大、此方法的栈帧长时间不能被回收、方法调用达不到JIT的编译条件)下才有效)
6. 局部变量没有类变量的“准备阶段”。
ii. 操作数栈
1. 例子:整数加法的字节码指令iadd在运行的时候会相加在操作数栈中最接近栈顶的两个元素。
iii. 动态连接
1. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,为了支持方法调用过程中的动态连接。(不懂)
iv. 方法返回地址
1. 发生异常时,没有在本方法的异常表中搜索到匹配的异常处理器,就会导致方法退出,不会给上层调用者返回任何值。
2. 方法退出的过程实际上等同于把当前栈帧出栈。
v. 附加信息(取决于具体的虚拟机实现)
2. 方法调用(不等同于方法执行,任务是确定被调用方法的版本。)
a) 解析
i. 在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用(主要为静态方法和私有方法两大类)。
ii. 能被invokestatic和invokespecial指令调用的方法有静态方法,私有方法,实例构造器,父类方法4类,都可以在解析阶段中确定唯一的调用版本。
iii. 解析调用一定是个静态的过程,在类转载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用。
b) 分派
i. 静态分派
1. Humanman = new Man(); Human为静态类型,Man为实际类型,在编译阶段,javac编译器会根据参数的静态类型决定使用哪个重载版本。
ii. 动态分派
1. Invokevirtuala指令的运行时解析过程:
a) 找到操作数栈顶第一个元素的实际类型,记作C
b) 如果在C中找到像符合方法,进行访问权限验证,通过则返回这个方法的直接引用,不通过则返回异常。
c) 否则按照继承关系向上查找。
d) 没有的话就返回异常。
iii. 单分派与多分派
iv. 虚拟机动态分派的实现
1. 创建一个虚方法表,257页
2. 方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。
c) Invokedynamic
i. 这条指令的第一个参数不再是代表方法符号引用的常量,而是变为JDK1.7新加入的CONSTANT_InvokeDynamic_info常量。
3. 基于栈的字节码解释执行引擎。
a) 基于栈的指令集架构。
b)