物理机执行引擎
直接建立在处理器,硬件,指令集和操作系统层面上
虚拟机执行引擎
可以自行制定指令集与执行引擎的结构体系
不同实现
局部变量表
是一组变量值存储空间,用于存放方法参数和方法内的局部变量
java程序编译为Class文件时,在方法的Code属性的max_locals中确定了局部变量表的最大容量
局部变量表以变量槽Slot为最小单位
Slot应该能够存放一个boolean,byte,char,short,int,float,reference或returnAddress类型数据
能直接或间接的查找到对象所属数据类型在方法区中存储的类型信息
returnAddress服务的jsr,jsr_w,和ret指令已由异常表替代
64位数据类型,虚拟机会以高位对齐方式为其分配两个连续Slot,由于局部变量表是线程私有的,所以无论读写两个连续Slot是否是原子操作,都不会引起数据安全问题
虚拟机使用局部变量表完成参数值到参数变量表的传递
实例方法第0位索引Slot默认用于传递对象实例的引用,可用this访问
局部变量表不像类变量那样存在赋0值的“准备阶段”
操作数栈
栈元素可以是任意Java数据类型
操作数栈深度不会超过max_stacks
栈元素数据类型必须与字节码指令序列严格匹配
大多数虚拟机栈帧会有一部分重叠,以优化方法调用
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
静态解析是指符号引用在类加载阶段或第一次使用的时候转化为直接引用
动态连接是指符号引用在每一次运行期间转化为直接引用
方法返回地址
退出方法的方式
正常完成出口,遇到方法返回字节码指令,是否有返回值由返回指令决定
返回到方法被调用位置
附加信息
解析
类加载的解析阶段会将一部分符号引用转化为直接引用的前提是方法在程序运行之前就由一个可确定版本,且运行期不可改变,这类调用称为解析
编译期可知,运行期不变
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一版本
分派
静态分派
Human man =new Man(),Human是man的静态类型
Human man =new Man(),Man是man的实际类型
静态分派发生在编译阶段,因此确定静态分派的动作不是虚拟机执行的
动态分派
在运行期根据实际类型确定方法执行版本的分派过程,称为动态分派
若C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,若通过则返回这个方法的直接引用,否则抛出异常
单分派和多分派
java是一门静态多分派,动态单分派的语言,编译时根据参数类型和接收者确定方法版本,运行时根据接收者实际类型确定方法版本
虚拟机分派的实现
动态类型语言支持
动态类型语言
jdk1.7与动态类型语言
jdk1.7以前的方法调用指令的第一个参数都是在编译时产生的被调用方法的符号引用
在此之前在java虚拟机上实现的动态类型语言方式蹩脚,影响性能
jdk1.7出现了invokedynamic指令以及java.lang.invoke包
java.lang.invoke包
提供了MethodHandle动态确定目标方法的机制,类似于函数指针
Reflection是在模拟java代码层次的方法调用,MethodHandle是在模拟字节码层次的方法调用
Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang.invoke.MethodHandle对象包含的信息多
虚拟机可以在MethodHandle机制方面在字节码层面做各种优化
MethodHandle可以服务于所有Java虚拟机之上的语言
invokedynamic指令
每一处含有invokedynamic指令的位置都称作"动态调用点",第一个参数不再是代表方法符号引用的CONSTANT_Method_info常量,而是变为jdk1.7新加入的CONSTANT_Method_info常量,从这个常量可以得到三项信息
有固定的参数,返回值是java.lang.invoke.CallSite对象,代表真正要执行的目标方法的调用
掌握方法分派规则
jdk1.7前4条调用指令的分派逻辑固化在虚拟机中程序员无法改变
invokedynamic它的分派逻辑不是由虚拟机决定而是由程序员决定