JVM读书笔记(五):虚拟机字节码执行引擎

来源:互联网 发布:网络咨询护士工资多少 编辑:程序博客网 时间:2024/06/05 15:12

虚拟机字节码执行引擎

一、概述

所有的java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,

输出的是执行结果。

二、运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构。

存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。

编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定,并且写入到方法表的Code属性之中

因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现

在活动线程中,只有位于栈顶的栈帧才是有效的

1、局部变量表

用于存放方法参数和方法内部定义的局部变量

局部变量表的容量以Slot为最小单位

通过索引定位的方式使用局部变量表,索引值范围是从0开始至局部变量表最大的Slot数量。
如果访问的是32为数据类型,索引n代表使用第n个slot。如果是64位数据,则会同时使用n
和n+1两个slot

如果执行的是实例方法(非static),则第0个slot默认用于传递方法所属实例的引用(this)

局部变量表的slot是可重用的,如果当前字节码PC计数器的值已经超出了某个变量的作用域,
那这个变量对应的slot就可以交给其他变量使用

这在一定程度上会影响GC,代码离开变量作用域后,需要对其进行读写复用操作才能对原来的
进行回收(手动赋值null的意义)

2、操作数栈

概念模型中两个栈帧是完全独立的,但实际会做一些优化,让两个栈帧出现一部分重叠

3、动态链接

指向运行时常量池中该帧所属方法的引用

Class文件的常量池中的符号引用,在类加载阶段或第一次使用时转化为直接引用,这种叫静态解析。另一部分在每一次
运行期间转化为直接引用,这部分叫动态链接。

4、方法返回地址

方法执行后,有两种方式可以退出方法:

  1. 正常完成出口:遇到任意一个方法返回的字节码指令
  2. 异常完成出口:在方法执行过程中遇到异常,并且没有在方法体中得到处理,以异常完成出口的方式退出不会给上层调用
    产生任何返回值

三、方法调用

方法调用并不等同与方法执行

方法调用阶段唯一的任务就是确定被调用方法的版本(确定哪一个方法)

一切方法调用在Class文件里面存储的都只是符号引用,而不是直接引用(动态链接)

1、解析

非虚方法:类加载的时候就会把符号引用解析为该方法的直接引用(在解析阶段中确定唯一的可用版本)
符合条件的:静态方法、私有方法、实例构造器、父类方法、final方法

解析调用一定是个静态的过程,在编译期间就完全确定

2、分派

  1. 静态分派(重载的本质)

关于重载:虚拟机在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的。

静态分派发生在编译期,由编译器执行,而非虚拟机

重载的版本不是唯一的,而是“更加合适的”,原因是字面量不需要定义,所以没有显式的静态类型

  1. 动态分派(重写的本质)

invokevirtual指令的多态查找过程:

(1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C

(2)如果在C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,通过则返回这个方法的直接引用

(3)否则,从下往上依次对C的各个父类进行上一步的搜索验证过程

(4)如果始终没有找到合适的方法,抛异常

动态分派:运行期根据实际类型确定方法执行版本的分派过程

  1. 单分派与多分派

静态多分派,动态单分派

  1. 虚拟机动态分派的实现

虚方法表:存放着各个方法的实际入口地址

如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,
都指向父类的实现入口。如果子类重写了这个方法,子类方法表中的地址将会替换为指向子类实现版本的入口地址。

3、动态类型语言支持

invoke包 与 invokedynamic指令

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

这边暂时不看

0 0
原创粉丝点击