细说JVM系列:虚拟机字节码执行引擎

时间:2022-05-27 10:01:44

虚拟机字节码执行引擎

字节码就像是汇编语言,是 JVM 的指令集。

代码编译的结果是从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。

概述

执行引擎是 Java 虚拟机最核心的组成部分之一。“虚拟机” 是一个相对于 “物理机” 的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行哪些不被硬件直接支持的指令集格式。

在 Java 虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,这个概念模型称为各种虚拟机执行引擎的统一外观(Facade)。在不同的虚拟机实现里面,执行引擎在执行 Java 代码的时候可能会有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,也可能两者兼备,甚至还可能会包含几个不同级别的编译器执行引擎。但从外观上看起来,所有的 Java 虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。

运行时栈帧

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

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

一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作,在概念模型上,典型的栈帧结构如图 8-1 所示。

细说JVM系列:虚拟机字节码执行引擎

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

细说JVM系列:虚拟机字节码执行引擎

Java程序在执行前先对程序源码进行词法分析和语法分析处理,把源码转化为抽象语法树。对于一门具体语言的实现来说,词法分析、语法分析以及后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是C/C++语言。当然也可以选择其中的一部分步骤实现一个半独立的编译器,这类代表是Java语言。又或者把这些步骤和执行引擎全部集中封装到一个封闭黑匣子中,如大多数的JS执行器。

基于栈的指令集与基于寄存器的指令集

Java编译器输出的指令流,基本上是一种基于栈指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。

基于栈的指令集主要优点就是可移植。除此之外,还有其他的优点,如代码相对更加紧凑(字节码中每个字节就对应一条指令,而多地址指令集中还需要存放参数)、编译器实现更加简单等。

缺点是:执行速度相对较慢。

通过一段代码来学习基于栈的解释器执行过程。


public int calc() {
int a = 100;
int b = 200;
int c = 300;
return (a+b) *c;
}

对应的字节码:

public int calc();
Code:
stack=2, Locals=4, Args_size=1
0: bipush 100 //将单字节的整数常量值(-128~`17)推入操作数栈
2: istore_1 //将操作数栈顶的整形值出栈并存放到第1个局部变量Slot中
3: sipush 200 //与0类似
6: istore_2
7: sipush 300
10:istore_3
11:iload_1 //将局部变量表第1个Slot的整型值复制到操作数栈顶
12:iload_2 //将局部变量表第2个Slot的整型值复制到操作数栈顶
13:iadd //将操作数栈中的头两个栈顶出栈,相加,再将结果入栈
14:iload_3 //把第3个Slot的300压入操作数栈
15:imul //将操作数栈中头两个栈顶出栈,相乘,将结果入栈
16:ireturn //结束方法执行,将操作数栈顶的整数返回

上面的示例可以看出中间变量都以操作数栈的入栈和出栈为信息交换途径。以上执行过程只是概念模型,实际执行时虚拟机会做优化。

基于栈的指令集与基于寄存器的指令集

android的虚拟机基于寄存器,java基于栈(内存)

基于栈的指令集:
优点:可以执行强一些
缺点:速度慢

基于寄存器:
与基于栈刚好相反