【笔记】深入理解Java虚拟机之虚拟机执行子系统 (3)

所有的Java虚拟机的执行引擎输入、输出都是一致的:输入的是字节码二进制流,处理过程是字节码解析执行的等效过程,输出的是执行结果。

1、运行时栈帧结构

1、虚拟机是以方法最为最基本的执行单元,“栈帧”是用于支持虚拟机进行方法调用和方法执行背后的数据结构。每一个方法从调用到执行结束的过程,都对应这一个栈帧在虚拟机栈里面从入栈到出栈的过程。
2、每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。
3、一个栈帧需要多少内存,取决于程序源码和其他虚拟机实现的栈内存布局形式。
4、对于执行引擎来说,只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是生效的,被称为“当前栈帧”,与这个栈帧所关联的方法叫做“当前方法”,执行引擎所运行的所有字节码只针对 当前栈帧进行操作。

在这里插入图片描述

1-1、局部变量表

  • 线程私有
  • 一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量
  • 方法的Code属性中max_locals 确定了该方法所需分配的局部变量表的最大容量;
  • 局部变量表是以变量槽为最小单位,每个变量槽能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据;
  • 针对于long 与double数据类型,虚拟机以高位对齐的方式,为其分配两个连续的变量槽空间
  • 当一个方法被调用的时,Java虚拟机使用局部变量表来完成参数值到参数变量列表的传递过程,即实参到形参的传递。如果执行的是实例方法,局部变量表的第0位索引变量槽是用于传递方法所属对象实例的引用,可通过this访问,其余按照参数表顺序排列。
  • 为了节省栈帧耗用的内存空间,局部变量表中变量槽是可以重用的,(因为方法体定义的变量的作用域并不一定覆盖整个方法体)
  • (关于不使用的变量主动赋值为null的反驳):(1、)以恰当的变量作用域控制变量回收时间才是最优雅的解决方式,(2、)即时编译才是虚拟机执行代码的主要方式,会被优化。

1-2、操作数栈

  • 操作数栈,一个后入先出栈,操作数栈的最大深度写在了max_stacks数据项中。
  • 当一个方法刚开始执行的时候,方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和读取数据,也就是出栈和入栈操作。
  • 基于栈的执行引擎。

1-3、动态连接

  • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用;
  • 字节码中的方法调用指令以常量池指向方法的符号引用为参数,这些符号引用一部分会在类加载阶段或者第一次使用的时候直接化为直接引用,这种转化被称为静态解析,另外一部分将在每一次运行期间都转化直接引用被称为动态连接;

1-4、方法返回地址

  • 正常调用完成
  • 异常调用完成
  • 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作栈数、把返回值(有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令;

2、方法调用

1、方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法)
2、一切方法调用在Class文件里面存储的都是符号引用,而不是方法在实际运行时内存布局中的入口地址(直接引用);

2-1、解析

  • 所有的方法调用的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种成立的前提:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法在运行期间是不可改变的
  • 符号上诉中的方法“编译器可知,运行期不可变”,主要有私有方法和静态方法两打类
  • 调用方法的指令:
    • invokestatic: 静态方法
    • invokespecial:构造方法,私有方法,父类的方法
    • invokevirtual:所有的虚方法
    • invokeinterface:接口方法,会在运行时再确定一个实现该接口的对象
    • invokedynamic: 先在运行时动态解析出调用点限定符所引用的方法,再执行该方法。
  • 被invokespecial和invokestatic指令调用的方法可以在解析阶段确定唯一的版本,(静态方法、构造方法,私有方法,父类的方法、被final修饰的方法【尽管被invokevirtual调用】)这些会解析为直接引用,这些方法统称为“非虚方法
  • 解析调用一定是一个静态过程,在编译期间就完全确定,另一种主要的方法调用为分派调用

2-2、分派

分派有:静态单分派、静态多分派、动态单分派、动态多分派;

  • 静态分派
    • (父类A)a= new (子类B()) A是变量的静态类型,B是变量的实际类型;
    • 静态类型是在编译期可知的,实际类型在运行期才可确定;
    • 编译器在重载时是通过参数的静态类型而不是实际类型作为判断依据的。由于静态类型编译期可知,所以在编译阶段,Javac编译器根据参数的静态类型决定使用哪个重载版本。
    • 所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。最典型应用表现是方法重载。静态分派发生在编译阶段。
  • 动态分派
    • 运行期间根据实际类型确定方法执行版本的分派过程称为动态分派
    • invokevirtual指令的运行时解析过程:
      • 找到操作数栈顶的第一个元素所指向对象的实际类型,记作C ;
      • 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回IllegalAccessError异常;
      • 否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程
      • 如果始终没有找到合适的方法,抛出AbstractMethodError异常;
    • Java语言重写的本质: invokevirtual 指令的第一步就是确定接收者的实际类型,根据方法的接收者的实例类型选择方法版本;
    • 这是虚方法执行invokevirtual指令的执行逻辑。字段不会参与多态,只对方法有效;
  • 单分派和多分派
  • 方法的接收者与方法的参数统称为方法的宗量;单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择
  • 唯一可以影响虚拟机选择的因素只有该方法的接收者的实际类型,只有一个宗量作为选择依据,所以Java的动态分派属于单分派类型;
  • Java是一门静态多分派,动态单分派的语言
  • 虚拟机动态分派的实现:查虚方法表等
  • 虚方法:Java对象里面的方法(不被final修饰)默认;即不允许被子类重写;

3、动态类型语言支持

3-1、java.lang.invoke

  • java.lang.invoke提供了一种新的动态确定目标方法的机制,称为”方法句柄“
  • Reflection和MethodHandle区别:
    • Reflection 是模拟Java代码层次的方法调用,MethodHandle是在模拟字节码层次的方法调用;
    • Reflection中Method包含的信息比MethodHandle多,Reflection重量级,MethodHandle轻量级;
    • MethodHandle对字节码的指令方法调用的模拟,虚拟机会有这方面的各种优化(方法内联)

3-2、invokedynamic

(转)invokedynamic

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

许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(解释器)和编译执行(通过即时编译器产生本地代码执行)两种选择;

4-1、解释执行

4-2、基于栈的指令集与基于寄存器的指令集

  • Javac编译器输出的字节码指令流,基本上是一种基于栈的指令集架构,可移植,理论上执行速度会慢一点

4-3、基于栈的解释器执行过程

参考书籍《深入理解JAVA虚拟机》第三版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值