不同虚拟机在执行Java代码的时候,会选择不同方式,有可能采用其中一种也有可能两种结合:
- 解释执行(通过解释器执行)
- 编译执行(通过即时编译器产生本地代码执行)
大纲
方法调用
栈帧结构
栈帧(StackFrame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
局部变量表
局部变量表的容量以变量槽(称Slot)为最小单位,每个Slot都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据。
操作数栈
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中所指的“栈”就是操作数栈。
动态连接
- 静态连接:在类加载阶段或者第一次使用的时候就转化为直接引用
- 动态连接:在每一次运行期间转化为直接引用
方法返回地址
- 正常完成出口:有给上层调用者产生返回值
- 异常完成出口:没有给上层调用者产生返回值
额外的附加信息
方法调用
概述
方法调用不等于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本,不涉及方法内部的具体运行过程。由于一切方法调用在Class文件里面存储的都只是符号引用。
解析调用
对于那些在编译期间(程序运行之前)就有可确定的调用版本,在运行期是不可改变的,在类加载的解析阶段,会将目标方法在Class文件的符号引用转化为直接引用,该类方法的调用成为解析调用。
该类方法称为非虚方法:包括静态方法、私有方法、实例构造器、父类方法。反着称为虚方法。
分派调用
不同于解析调用,是静态的过程。分派调用,可能是静态(编译期)也有可能是动态(运行期)。
静态分派
- 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。
- 典型应用是方法重载。
动态分派
- 在运行期间,根据实际类型确定方法执行版本的分派称为动态分派。
- 典型应用是方法重写。
虚拟机动态分配实现
在类的方法区中建立一个虚方法表,存放着各个方法的实际入口地址,使用虚方法表索引来代替元数据查找可以提高性能。
如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口。
单分派和多分派
目前来说,Java于洋是一门静态多分派、动态单分派的语言。
动态类型语言支持
JDK7新增invokedynamic
指令实现“动态类型语言”支持,为JDK8实现Lambda表达式做技术准备。
动态类型语言
动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译器,变量无类型而变量值才有类型。
静态类型与动态类型对比:
- 静态类型语言:在编译器已将方法完整的符号引用生成出来,作为方法调用指令的参数存储到Class文件中。这个方法引用包含次方法定义在哪个具体类型之中等信息,通过该符号引用,虚拟机可以翻译出这个方法的直接引用
- 动态类型语言:变量本身没有类型的,变量的值才有类型,编译时只能确定方法名称,参数,返回值,而不会去确定方法所在的具体类型
字节码执行(基于栈的解释执行引擎)
编译过程
基于栈的指令集与基于寄存器的指令集
基于栈的指令集
优点
- 可移植,用户程序不会直接使用寄存器。访问频繁的数据,虚拟机可以自行决定再放到寄存器中提高性能。
- 代码相对更加紧凑
- 编译器实现更加简单
缺点
- 执行速度相对来说慢一点