虚拟机字节码执行引擎

来源:互联网 发布:澳门网络博客 编辑:程序博客网 时间:2024/05/16 07:17

1、运行时栈帧结构

栈帧是支持虚拟机进行方法调用和方法执行的数据结构,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等。每一个方法从调用开始到执行完成的过程,都是一个栈帧在虚拟机栈入栈、出栈的过程。

一个线程中的方法调用链可能很长,很多方法处于执行状态。但是对于虚拟机来说,在活动线程中,只有处于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法叫做当前方法。执行引擎运行的所有字节码指令都是针对当前栈帧的
这里写图片描述

1.1 局部变量表

用于存放方法参数和方法中的局部变量。在java被编译成class文件时,就在code属性的max_locals中确定了该方法所需要的最大空间。局部变量表的容量以变量槽slot为最小单位,每个变量盛放在slot中

在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程。如果方法是实例方法,那么参数变量列表的第0个传递的是方法所属的对象。

局部变量需要手动赋初始值

为了节省栈帧空间,局部变量表对slot进行重用。如果方法内有代码块,出了代码块,代码块中局部变量的slot将被重用。
详见<深入理解java虚拟机>第二版 8.2.1

1.2 操作数栈

是一种栈结构,存放操作数。如1+2,栈中存放1和2,使用iadd指令将栈顶的1,2取出相加,再将结果入栈。操作数栈的深度在编译期已经确定,放在code结构的max_stacks中。
在概念模型中,两个栈帧的虚拟机栈元素是完全独立的,但是大多数虚拟机会做优化,让两个栈帧出现一部分的重叠,这样可以共用一部分数据,不需要进行额外的参数复制。

1.3 动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态连接

1.4 方法返回地址

当一个方法执行,有两种方式退出这个方法
1.4.1 遇到任意一个方法返回的字节指令。这种方式叫做正常完成出口,这种方式会将返回值传递给上层
1.4.2 遇到没有处理的异常,称为异常完成出口。不会给上层返回值
方法退出后,正常完成出口PC计数器会返回调用者的地址继续执行。异常完成出口会根据异常处理表来确定
方法退出就相当于当前栈帧出栈,然后恢复上个栈帧的数据,把返回值压栈,调整PC计数器

1.5 栈帧信息

一般指动态连接,方法返回地址和其他附加信息

2、方法调用

2.1 解析

调用的方法在程序代码写好,编译器编译时就已经确定下来,这类方法叫做解析。这类方法主要有静态方法和私有方法。

java虚拟机提供了5条方法调用的字节码指令:

invokestatic 调用静态方法
invokespecial 调用构造方法、私有方法、父类方法
invokevirtual 调用虚方法
invokeinterface 调用接口方法
invokedynamic 运行时动态解析出限定符所引用的方法,然后执行

被invokestatic/invokespecial调用的方法在解析阶段都是唯一的,它们有静态方法、私有方法、构造方法、父类方法。这些称为非虚方法,其他称为虚方法(final除外)

2.2 分派

分派有静态分派、动态分派、单分派、多分派
2.2.1 静态分派 对应重载

class Human{}class Man extends Human{}Human human = new Man();

其中Human称为human的静态类型或外观类型、Man称为human的实际类型。
静态类型在编译期可知的,且变量本身的静态变量不会被改变
实际类型变化的结果在运行期才能确定。
编译器在重载时是根据参数的静态类型而不是实际类型判定的,因此在编译阶段,编译器已经确定了使用哪个重载版本

在多数情况下,重载的版本不是唯一的,而是确定一个更加合适的版本。详见<深入理解java虚拟机 第二版> P249

2.2.2 动态分派 重写
动态分派使用对象的实际类型。在实际类型查找方法,如果找到且有访问权限,则直接调用。否则,在父类中查找。这就是重写的本质

2.2.3 单分派与多分派

public class Test {    static class QQ{}    static class _360{}    static class Father{        void choice(QQ qq){};        void choice(_360 arg){};    }    static class Son extends Father{        void choice(QQ qq){};        void choice(_360 arg){};    }    public static void main(String[] args) {        Father father = new Father();        Father son = new Son();        father.choice(new _360());        son.choice(new QQ());    }}

方法的接收者和方法的参数统称为方法的宗量。
编译阶段,即静态分派阶段,都是根据静态类型调用,son的静态类型也是Father:Father.choice(new _360());和Father.choice(new QQ());
因为根据两个宗量(个人理解是参数类型)进行选择,所以静态分派是多分派。

运行阶段,即动态分派阶段,因为编译阶段已经确定了参数类型,所以不需要考虑参数的类型。影响虚拟机选择方法的只有此方法接收者的实际类型。因为只有一个宗量(参数类型),所以动态分派是单分派

2.2.4 动态分派实现
虚拟机的动态分派是非常频繁的动作,所以对其进行了优化。比较稳定的优化方案是在类的方法区中建立虚方法表。
虚方法表存放虚方法的实际入口,如果子类没有实现,则存放父类方法的入口。
方法表一般在类加载的连接阶段初始化,准备了类变量的初始值后,虚拟机会把该类的方法表也初始化完毕

2.3 动态类型语言

动态类型语言的特征是 类型检查是在运行期,而不是在编译期。
在编译期进行类型检查的称为静态类型语言 如java、c++

2.3.1 java.lang.invoke包
动态确定目标方法的机制:MethodHandler
MethodHandler的实现是在字节码层次,且虚拟机已经对此方法做过优化。与反射类似,但是反射是在上层代码的层次

2.3.2 inovkedynamic
为了实现动态语言,把查找目标方法的决定权从虚拟机转嫁到用户手里。

3、基于栈的字节码解释执行引擎

3.1 解释执行

这里写图片描述

3.2 基于栈的指令集和基于寄存器的指令集

基于栈的指令集 较好的可移植性,但是效率较寄存器低
基于寄存器的指令集 移植性差,但是效率较高

原创粉丝点击