JVM——虚拟机字节码执行引擎

13 篇文章 0 订阅

概述

执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是相对于一个“物理机”的概念,这两种都有代码执行能力,其区别是物理机的执行是直接建立在处理器、硬件、指令和操作系统层面的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行那些不被硬件直接支持的指令集格式。
在这里插入图片描述
不同虚拟机的实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,也可能两者兼备,甚至还看会包含几个不同级别的编译器执行引擎。从外观看,所有Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析等效过程,输出的是执行结果。

运行时栈帧结构

**栈帧(Stack Frame)是用于支持虚拟机进行非法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。**栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每个方法从调用开始到执行完成的过程,都对应这一个栈帧在虚拟机栈里面从入栈到出栈的过程。

每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的code属性之中,因此一个栈帧需要分配多深内存,不会受到程序运行期变量数据影响,而仅仅取决于虚拟机实现。

一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。

概念模型图
在这里插入图片描述
局部变量表

局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件是,方法的Code属性的max_locals数据项中确定了该方法需要分配的局部变量表的最大容量。

  • 局部变量表的容量以变量槽(Variable Slot),Solt为最小单位。
  • 局部变量能存储8中数据类型:boolean、byte、char、short、int、float、reference、returnAddress。
  • 一个Solt可以存放32位以内的数据,Java中占用32位以内的数据类型有:boolean、byte、char、short、int、float、reference、returnAddress。
  • reference类型表示一个对象实例的引用,虚拟机规范既没有说明它的长度,也没有明确指出这种引用应用怎样的结果。但一般来说,虚拟机实现至少都应当能通过这个引用做到两点:一是从此引用中直接或间接查找到对象在Java堆中的数据存放的起始地址索引,二是此引用直接或间接查到对象所属数据类型在方法区中的存储信息。
  • 64位的数据类型为long和double,虚拟机中采用读写分割为2次32位读写的做法来实现。
  • 虚拟机通过索引定位的方式使用局部变量表,索引值从0到局部变量表最大的Slot,访问一个32位数据类型的变量,索引n代表第n个Slot,64位数据类型的变量,读取n和n+1的Slot。对于两个相邻的存放在64位数据的Slot,不允许单独访问。
  • 局部变量表中的Slot是可以重用的,方法体中定义的变量其作用域并不一定会覆盖整个方法,如果当前字节码计数器PC的值已经超出了某个变量的作用域,那这个变量对于的Slot交给其余变量使用。
  • 类变量有两次赋予初始值的过程,一次在准备阶段,系统赋予初始值,另外一次在初始化阶段,赋予程序员定义的初始值,所以类变量没有为类定义初始值没关系,类变量仍然有初始值。局部变量不会被赋予初始值要使用,必须赋予初始值。
public static void main(String[] args){
	int a;
	System.out.println(a);       //编译报错
}

操作数栈

操作数栈(Operand Stack)也称为操作栈,它是一个后入先出(Last In First Out,FIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double.32为数据类型所占的栈容量为1,64位数据类型所占栈容量为2.在方法执行的任何时候,操作数栈的深度都不会超过max_stacks数据项设定的最大值。

  • 方法开始执行时,操作数栈为空,执行过程中,会有各种字节码指令往操作数栈写入和提取内容,也就是入栈/出栈过程。
  • 在概念模型中,两个栈帧作为虚拟机栈的元素,相互独立的。但是虚拟机会做优化,令两个栈帧的部分操作数栈重叠,这样进行非法调用时,共用一部分数据,无须进行额外的参数复制传递。
    在这里插入图片描述

动态连接

每个栈帧都包括一个指向运行时常量池中该栈帧所属非法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。Class文件中的常量池存在大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这些称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。

方法返回地址

当一个方法执行后,只有2中方法可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者),是否有返回值和返回的类型根据遇到何种方法返回指令来决定,这种退出方法的方式称为正常完成出口。

另一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要本地方法的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口。一个方法使用异常完成出口的方式退出,是不会给他的上层调用者产生任何返回值。

退出的方式等同于把当前栈帧出栈,因此退出时可能只需的操作有:回复上层方法的局部变量表和操作数栈,把返回值亚茹调用者栈帧的操作数栈中,调用PC计数器的值以指向方法调用指令后面的一条指令等。

附加信息

虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧制作,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值