目录
一、栈帧
1. 栈帧结构
2. 基于栈的解释执行过程
二、方法调用
1. 方法调用指令
2. 分派
三、动态类型语言
四、参考资料
一、栈帧
1. 栈帧结构
栈帧是Java虚拟机栈进行方法调用和执行的数据结构,是方法最基本的执行单元,是栈的元素。一个栈帧分配多大内存,则不受运行期影响,由程序源码和占内存布局决定(内存大小编译期可知)。每一个方法的调用开始和结束,都对应JVM栈的元素(栈帧)的入栈和出栈。
处于Java虚拟机栈顶的栈帧,称为“当前栈帧”;当前栈帧所关联的方法,称为“当前方法”。从Java程序层面看,同一时刻、同一线程里,栈的所有方法都处于执行状态;从执行引擎层面看,只有处于栈顶的方法才是执行状态。
栈帧的结构包含:局部变量表、操作数栈、动态连接、返回地址、附加信息,如下表所示。注意:方法调用时,用局部变量表完成参数值到参数变量列表的传递过程,即:实参到形参的传递。
栈帧结构 | 作用 | 特点 |
局部变量表 (Local Variables Table) | 变量值的存储空间 | 1.存储:方法参数及方法定义的局部变量; 2.局部变量表容量以变量槽(slot)为最小单位; 3.方法Code属性max_locals决定最大容量(slot数量); 4.每个slot都能存储boolean、byte、char、short、int、float、returnAddress类型的数据; 5.64位JVM时,只有long、double会以高位对齐的方式分配两个连续的slot空间; 6.实例方法时,第0位slot是this参数;其他:方法参数、方法局部变量的顺序; 7.slot被回收重用的根本原因:slot中是否还存有对象的引用或值。 |
操作数栈 (Operand Stack) | 操作数据的栈 | 1.JVM解释执行引擎称为“基于栈的操作引擎”; 2.栈的最大深度由编译器Code属性的max_stacks决定; 3.字节码指令往栈中写入和读取操作数,即:操作数的入栈和出栈; 4.栈中元素数据类型与字节码指令的类型必须严格匹配; 5.模型中JVM栈的元素(栈帧)是相互独立的,但是JVM实际优化时,可能会有重叠区域。 |
动态连接 (Dynamic Linking) | 栈帧所属方法的引用 | 1.每个栈帧都包含一个指向常量池该栈帧所属方法的引用(反过来,字节码的方法调用以常量池的符号引用作为参数); 2.静态解析:编译器符号引用转直接引用; 动态连接:每次运行时符号引用转直接引用。 |
返回地址 (Return Address) | 返回给方法调用者 | 1.两种方式退出当前方法: “正常调用完成”:执行引擎遇到方法的返回指令; “异常调用完成”:当前方法的异常表中没有搜索到匹配的异常,则异常退出(不会有任何返回值); 2.方法正常退出时,一般主调方法的PC计数值可作为返回地址,若有返回值则压入主调方法的栈帧中; |
附加信息 | 其他信息 | 如调试、性能收集相关的信息 |
其中,reference数据类型的作用:
- 根据直接引用或间接引用查找到堆中实例对象的起始地址或索引;
- 根据直接引用或间接引用查找到元空间(方法区)的类型信息(反射机制获取Class信息);
2. 基于栈的解释执行过程
指令集架构分类如下,Java是基于栈的指令集架构。
- 基于栈的指令集架构:字节码指令流大部分都是零地址指令,依赖操作数栈工作,其优点:可移植;
- 基于寄存器的指令集架构:依赖于寄存器来访问和存储数据,如:主流PC机(x86二地址指令集)
执行字节码有两种选择(或两着兼备):解释执行(解释器)、编译执行(即时编译产生本地机器代码),下面是基于栈的解释执行过程实例、源码和class文件如下。指令含义参考《HotSpot虚拟机之Class文件及字节码指令》。
public int calc() {int a = 100;int b = 100;int c = 100;return (a + b) * c;
}
public int calc();descriptor: ()Iflags: ACC_PUBLICCode:stack=2, locals=4, args_size=10: bipush 1002: istore_13: bipush 1005: istore_26: bipush 1008: istore_39: iload_110: iload_211: iadd12: iload_313: imul14: ireturnLineNumberTable:line 29: 0line 30: 3line 31: 6line 32: 9LocalVariableTable:Start Length Slot Name Signature0 15 0 this Lcom/cmmon/instance/classLoad/NotInitialization;3 12 1 a I6 9 2 b I9 6 3 c I
二、方法调用
1. 方法调用指令
方法调用阶段唯一目的是确定被调用方法的版本,即:调用哪个方法,其指令有5种:invokestatic、invokespecial、invokevirtual、invokeinterface、invokedynamic,如下表所示。
方法调用指令 | 特点 |
invokestatic | 作用:调用静态方法; |
invokespecial | 作用:调用实例构造器<init>()方法、私有方法、父类中的方法; |
invokevirtual | 1.作用:调用所有的虚方法; 2.该指令:动态分派实现方法复写。 |
invokeinterface | 作用:调用接口方法,运行时确定一个实现该接口的方法; |
invokedynamic | 1.作用:动态调用方法; 2.运行时动态解析动态解析调用点限定符所引用的方法,再去执行; |
注意: a.invokestatic、invokespecial、invokevirtual、invokeinterface其分派逻辑固化在JVM内部;而invokedynamic分派逻辑由用户设定的引导方法来决定; b.只要能被invokestatic、invokespecial调用的方法,在编译器可以确定方法版本,如:静态、实例构造器<init>()、私有、父类、final修饰的方法; c.final修饰的方法虽然用invokevirtual调用,但它是非虚方法。 |
invokevirtual指令不仅把常量池的方法符号引用解析为直接引用,同时根据方法接收者的实际类型来选择方法版本。invokevirtual指令解析过程,如下步骤:
- step1:找到操作数栈顶元素指向的对象的实际类型C;
- step2:类型C中查找与当前常量池匹配的方法时,且访问权限校验,通过则返回该方法,查找结束;否则抛出异常:java.lang.IllegalAccessError;
- step3:否则,从下往上的继承关系对C的父类,进行搜索和验证;
- step4:否则,没有查找到匹配的方法,抛出异常:java.lang.AbstractMethodError。
2. 分派
分派揭示Java多态性的实现,即:如重载、重写是如何实现。其分类:静态分派、动态分派或是单分派、多分派,如下表所示。
分派分类 | 概念 | 应用 | 特点 |
静态分派 | 所有依赖静态类型来决定方法版本的分派动作 | 方法重载 | 1.静态分派发生在编译期; 2.重载版本多个时,选择最合适的顺序:自动类型转换、自动装箱、装箱实现接口、父类(从下往上)、可变参数方法。 |
动态分派 | 运行期根据实际类型来决定方法版本的分派动作 | 方法重写 | 1.动态分派发生在运行期; 2.由invokevirtual指令实现。 |
注意: a.只有方法有多态,而字段永远不参与多态; b.变量的“静态类型”和“实际类型”:(都可能变化,但是变化时期不同) 静态类型:变化在使用时发生,且变量本身的类型不会改变,且最终在编译期可知; 实际类型:变化结果在运行期确定,编译期不知道一个对象的实际类型是什么; c.类型“宽化转换”:chart > int > long > float > double(不会匹配byte、short类型); d.“方法的宗量”:方法接收者、方法的参数: 单分派:根据一个宗量对目标方法进行选择; 多分派:根据多于一个宗量对目标方法进行选择。 |
注意:方法重写的本质是不仅把常量池的方法符号引用解析为直接引用,同时根据方法接收者的实际类型来选择方法版本;字段不参与多态,但子类声明与父类的相同的字段时,子类内存中两个字段都会存在,但子类会遮蔽父类的同名字段。
方法重写实现多态,在运行时频繁从元数据进行查找,解决该问题的方法:invokevirtual的虚方法表;invokeinterface的接口方法表。虚方法表目的是存储该类各个方法的实际入口地址,如下所示其特点。
三、动态类型语言
JDK7增加了动态类型指令invokedynamic,其类型检查的主过程在运行期,而不是编译期。
四、参考资料
HotSpot虚拟机之Class文件及字节码指令_爱我所爱0505的博客-CSDN博客
HotSpot虚拟机之类加载过程及类加载器_爱我所爱0505的博客-CSDN博客
百度安全验证
【Java 虚拟机原理】栈帧 | 动态链接 | 方法区 | 字节码文件二进制分析-腾讯云开发者社区-腾讯云
Java虚拟机运行时栈帧结构_java栈帧_爱躺平的咸鱼的博客-CSDN博客
【Java -- 虚拟器】方法分派模型 -- 静态分派、动态分派_51CTO博客_java虚拟器
Java中的静态分派和动态分派原理_51CTO博客_静态分派