欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java虚拟机(十三)------基于栈的字节码解释执行

程序员文章站 2022-04-20 17:22:47
...

当知道虚拟机是如何调用方法之后,接着就主要关注虚拟机是如何执行方法中的字节码指令的。 许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择,本文主要探讨Java在解释执行时,虚拟机执行引擎是如何工作的。

解释执行

Java语言经常被人们定位为“解释执行”的语言,在Java初生的JDK 1.0时代,这种定义还算是比较准确的,但当主流的虚拟机中都包含了即时编译器后,Class文件中的代码到底会被解释执行还是编译执行,就成了只有虚拟机自己才能准确判断的事情。 后来Java也发展出了可以直接生成本地代码的编译器(如GCJ),而C/C++语言也出现了通过解释器执行的版本(如CINT),再笼统地说“解释执行”,对于整个Java语言来说就成了几乎是没有意义的概念,只有确定了谈论对象是某种具体的Java实现版本和执行引擎运行模式时,谈解释执行还是编译执行才会比较确切。

大部分的程序代码到物理机的目标代码或虚拟机能执行的指令集之前,都需要经过下图中的各个步骤。 图中最下面那条分支,就是传统编译原理中程序代码到目标机器代码的生成过程,而中间的那条分支就是解释执行的过程。 对于一门具体语言的实现来说,词法分析、 语法分析以至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是C/C++语言, 也可以选择把其中一部分步骤(如生成抽象语法树之前的步骤)实现为一个半独立的编译器,这类代表是Java语言。
Java虚拟机(十三)------基于栈的字节码解释执行
Java语言中,Javac编译器完成了程序代码经过词法分析、 语法分析到抽象语法树,再遍历语法树生成线性的字节码指令流的过程。 因为这一部分动作是在Java虚拟机之外进行的,而解释器在虚拟机的内部,所以Java程序的编译就是半独立的实现。

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

Java编译器输出的指令流,基本上是一种基于栈的指令集架构,指令流中的指令大部分都是零地址指令,它们依赖操作数栈进行工作。基于寄存器的指令集,就是现在我们主流PC机中直接支持的指令集架构,这些指令依赖寄存器进行工作。 两者之间有什么不同呢?举个最简单的例子

如下分别使用两种指令集计算“1+1”的结果:

  • 基于栈的指令集:
iconst_1
iconst_1
iadd
istore_0

两条iconst_1指令连续把两个常量1压入操作栈后,iadd指令把栈顶的两个值出栈、相加,然后把结果放回栈顶,最后istore_0把栈顶值放到局部变量表的第0个Slot中。

  • 基于寄存器:
mov eax,1
add eax,1

mov指令把EAX寄存器的值设为1,然后add指令再把值加1,结果就保存在EAX寄存器里面。

两套指令集的优缺点

基于栈的指令集主要的优点就是可移植性,不会受硬件的不同而受影响。而基于寄存器的指令集,程序直接依赖这些硬件寄存器,不同的硬件设备,则不可避免受到约束。而使用栈架构的指令集,用户程序不会直接使用这些寄存器,而是由虚拟机来完成与寄存器的交互,从而避免直接与硬件交互。但基于栈指令集的主要缺点是执行速度相对来说会稍慢一些。而相对的基于寄存器指令集的执行速度会相对较优。

基于栈的解释器执行过程

通过如下代码解释基于栈的解释器执行过程:

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

使用javap命令后得如下反编译代码:

  public int calc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        100
         2: istore_1
         3: sipush        200
         6: istore_2
         7: sipush        300
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: imul
        16: ireturn

javap提示这段代码需要深度为2的操作数栈4个Slot的局部变量空间,根据这些信息总共可绘制7张图来描述程序执行过程中的代码、 操作数栈和局部变量表的变化情况:

Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行
Java虚拟机(十三)------基于栈的字节码解释执行