执行引擎-概述

概述

什么是执行引擎?

  • 定义:[采用 软件模拟 的物理机处理器就是执行引擎]{.red}

    • 物理机中的计算核心是处理器(CPU)
    • 虚拟机作为对物理机的模拟,执行引擎显然就是对计算核心的模拟,也就是处理器
  • 作用:[将(前端)编译器生成的字节码 翻译/编译 成本地机器指令]{.red}

    • 执行引擎每次都取出一条字节码指令并执行
    • 但是无论执行任何指令,最终都要依靠物理机,所以需要向物理机解释
    • 执行引擎需要将字节码指令翻译/编译成处理器能够识别的机器指令

    :::info

    ① 前端编译器是什么之后将会提到,可以暂时先认为前端编译器就是 Javac

    ② 前端编译器并不是执行引擎的其中一部分,特别注意

    :::

  • 特点:

    • [执行引擎作为对于处理的模拟:具有相应的指令集和缓存结构]{.red}

      • 执行引擎的指令集:[字节码指令]{.blue}
      • 执行引擎的缓存结构:[即时编译器产生的本地代码就缓存再方法区中]{.blue}
    • 执行引擎处理指令集的方式:

      • 解释执行:初代 Java 虚拟机——Classic 虚拟机就只拥有解释器,仅能够解释执行
      • 即时编译:BEA 开发的 JRockit 虚拟机就仅具有即时编译器,只能够编译执行
      • [解释执行 + 即时编译:HotSpot 虚拟机既具有即时编译器和解释器,两者可以协调合作]{.red}

      :::info

      注:什么是解释执行,什么是即时编译之后会详细提到

      :::

    • [Java 虚拟机执行引擎是基于栈执行的,现代计算机基本都是基于寄存器执行的]{.red}

      :::primary

      想要了解什么是基于栈执行的执行引擎,参考 运行时数据区-栈空间-操作数栈 部分

      :::

执行引擎是由哪些部分组成的?

:::warning

① 仅针对 HotSpot 虚拟机的执行引擎,而不针对其他虚拟机的执行引擎

② 下面的图示是将执行引擎的大多数细节都填充进去了,目前你只需要知道个大致组成就行,之后会详细介绍

:::

  • 图示

    执行引擎
  • 组成:[解释器 + 即时编译器 + 垃圾回收器]{.red}

解释器(解释执行)与编译器(编译执行)到底是什么?

:::warning

① 如果想要详细了解编译器,建议阅读《编译原理》(俗称龙书)

② 如果想要了解机器指令和汇编语言,建议阅读《深入理解计算机系统》和《数字设计和计算机体系结构》

:::

  • 编译器(编译执行):

    • 定义:[编译器将高级语言编译成处理器能够识别的机器指令]{.red}

    • 主要语言:C、C++ 都是编译执行的语言

    • 流程(任何类型的编译器基本都遵照以下流程):

      编译器流程
    • 分类(仅限于 Java):

      • 前端编译器:

        • 定义:[负责将源代码编译成 字节码指令]{.red}
        • 常见编译器:JDK 自带的 Javac 编译器,Eclipse JDT 提供的 ECJ(增量式编译器)
          • [Javac 编译器完全采用 Java 实现]{.blue}
          • Eclipse 编辑器中使用的就是 ECJ 编译器
      • 即时编译器(后端编译器)

        • 定义:[负责在进程运行时将经常调用的方法编译成 机器指令]{.red}

          • [编译生成的本地代码将会被缓存再方法区中(JIT 代码缓存)]{.red}
        • 常见编译器:HotSpot 提供的C1(客户端编译器)、C2(服务器端编译器),Graal 虚拟机提供的 Graal 编译器

          Graal 目前还只是实验性质的虚拟机,并没有发布正式的版本

      • 提前编译器(后端编译器)

        • 定义:[直接 将源代码编译成机器指令]{.red}
          • 类似于 C/C++ 使用的 GCC 编译器
        • 常见编译器:JDK 自带的 Jaotc 编译器,GNU 计划中的 GCJ 编译器,Excelsior JET 编译器
  • 解释器(解释执行):

    • 定义:[解释器每次都会向处理器解释源代码的含义]{.red}

    • 主要语言:Python、JavaScript、PHP 都是解释执行的语言

    • 流程

      解释器
    • 分类(仅限于 Java):

      • [字节码解释器:采用纯软件模拟的方式执行字节码 -> 效率非常低下]{.red}
      • [模板解释器:每条字节码都和一个模板函数关联,模板函数能够直接生成字节码对应的本地代码 -> 效率相对较高]{.red}
  • 区别:

    • [编译器对源代码进行编译之后会生成可执行文件,解释器不会生成任何可执行文件]{.red}
    • [编译器在编译过程中会对源代码采用各种优化技术进行优化(诸如逃逸分析),解释器不会对源代码进行任何优化]{.red}
    • 源代码再次执行时:
      • 编译器生成的可执行文件就可以直接使用不需要再编译,[所以编译执行效率非常高]{.blue}
      • 解释器则需要再次向处理器解释源代码的含义,[所以解释执行的效率就低]{.blue}
    • [编译器需要对源代码进行编译:程序的启动速度慢;解释器不需要任何编译过程直接向处理器解释:启动速度快]{.red}

+++danger Java 到底是解释执行还是编译执行的语言呢?

① 远古时代的 Java 确实是纯解释执行的语言,就和 Python 一样

② 如今的 Java 既不是纯解释执行也不是纯编译执行的语言,而是半解释半编译的语言

③ 如今的 Java 程序在运行过程会交替使用解释器和编译器,使得运行效率大幅提升(至于如何交替使用,会在后端编译器部分中详细介绍)

+++

执行引擎如何使用解释器和即时编译器呢?

  • [前端编译器将源代码编译成字节码]{.red}

    • 提前编译器通常会给即时编译器作为缓存加速,改善程序的启动时间
  • 执行引擎获取程序计数器提供的指令地址

  • 执行引擎从方法区中获取相应的字节码指令

  • [执行引擎开始协调解释器和即时编译器执行:]{.red}

    • [使用 热点探测技术 判断字节码指令是否要编译成本地代码]{.blue}

      • 如果执行引擎决定将其编译为本地代码,那么即时编译器将开始执行编译过程

        编译期间,[解释器会和即时编译器 异步执行]{.blue},避免浪费用户线程的执行时间

      • 如果执行引擎不认为要将其编译为本地代码,那么就采用解释执行

    • 查看调用的方法是否存在编译好的本地代码

      • 如果被调用的方法没有被编译成本地代码,那么将会解释执行
      • 如果被调用的方法已经有相应的本地代码,那么将会直接执行本地代码
执行引擎执行大致流程

+++danger 最后抛出一个问题,为什么要使用字节码文件呢?明明虚拟机已经实现跨平台了啊?

+++

Author: Fuyusakaiori
Link: http://example.com/2021/09/24/jvm/execution-engine/概述/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.