1. JVM编译器

1.1. 前端编译器

如javac,将java文件编译为class文件。此阶段不会发生指令重排序。

1.2. 后端编译器

JIT 是将一些字节码编译为机器码,并存入 Code Cache,下次遇到相同的代码,直接执行,无需再编译。此阶段可能会发生指令重排序。 和解释器一样,起点都是编译后的 class 文件,而不是.java 源文件。

HotSpot中内置了两个即时编译器,分别称为 Client Compiler和 Server Compiler ,或者简称为 C1 编译器和 C2 编译器。目前的 HotSpot 编译器默认的是解释器和其中一个即时编译器配合的方式工作,具体是哪一个编译器,取决于虚拟机运行的模式,HotSpot 虚拟机会根据自身版本与计算机的硬件性能自动选择运行模式,用户也可以使用 -client 和 -server 参数强制指定虚拟机运行在 Client 模式或者 Server 模式。这种配合使用的方式称为“混合模式”(Mixed Mode),用户可以使用参数 -Xint 强制虚拟机运行于 “解释模式”(Interpreted Mode),这时候编译器完全不介入工作。另外,使用 -Xcomp 强制虚拟机运行于 “编译模式”(Compiled Mode),这时候将优先采用编译方式执行,但是解释器仍然要在编译无法进行的情况下接入执行过程。通过虚拟机 -version 命令可以查看当前默认的运行模式。

C2编译器是专门面向服务端的,并为服务端的性能配置特别调整过的编译器,也是一个充分优化过的高级编译器,几乎能达到 GNU C++ 编译器使用-O2 参数时的优化强度,它会执行所有的经典的优化动作,如 无用代码消除(Dead Code Elimination)、循环展开(Loop Unrolling)、循环表达式外提(Loop Expression Hoisting)、消除公共子表达式(Common Subexpression Elimination)、常量传播(Constant Propagation)、基本块冲排序(Basic Block Reordering)等,还会实施一些与 Java 语言特性密切相关的优化技术,如范围检查消除(Range Check Elimination)、空值检查消除(Null Check Elimination ,不过并非所有的空值检查消除都是依赖编译器优化的,有一些是在代码运行过程中自动优化 了)等。

2. JVM解释器

https://cloud.tencent.com/developer/article/1398588

hotspot解释器模块(hotspot\src\share\vm\interpreter)有两个实现:基于C++的解释器和基于汇编的模板解释器。hotspot默认使用比较快的模板解释器。 其中

C++解释器 = bytecodeInterpreter* + cppInterpreter*
模板解释器 = templateTable* + templateInterpreter*

它们前者负责字节码的解释,后者负责解释器的运行时,共同完成解释功能。这里我们只关注模板解释器。

  • C++解释器 = bytecodeInterpreter* + cppInterpreter* 零汇编:只和编译器有关,与CPU无关
    volatile:语义就是禁止编译器优化

  • 模板解释器 = templateTable* + templateInterpreter* 涉及CPU指令和汇编

volatile:lock的作用锁缓存行、刷MOB(ROB和StoreBuffer)

3. JVM执行器

解释执行:包括C++解释器和模板解释器。解释执行并不是每次执行字节码时动态把它编译成机器码,而是将根据字节码的类型,转到对应的机器码去执行,即一个派发(switch)的过程。而C++解释器派发到的是由字节码对应的C++代码所编译成的机器码,模板解释器派发到的是字节码对应的汇编模板所生成的机器码。由于C++代码由编译器编译成机器码,比较冗余,所以执行速度慢,而模板解释器的汇编模板是直接由汇编代码专门编写的,执行效率高。解释执行执行速度较慢,并不是每次将字节码动态编码生成机器码的原因,这是错误的。而是对于每个字节码都派发到对应的机器码上执行,而不是从上到下的顺序执行机器码,多了很多判断、跳转的指令,所以效率较低。

编译执行:JIT对于热点代码,编译成运行效率高的机器码。这里与模板解释器的区别在于JIT针对的是代码段生成机器码,而模板解释器是针对每个字节码指令生成机器码,以及JIT是动态生成的,模板解释器是在JVM启动时就把字节码对应的汇编模板转换为机器码。某种意义上模板解释器也属于JIT的范畴。当JIT把整段代码直接编译成机器码时,在执行时就可以自上而下的获取执行机器码,而不用对于每条字节码指令跳转到对应的机器码上,执行效率获得提升。

4. 参考与感谢

[[../../../../cubox/006-ChromeCapture/JVM的编译器和解释器(随笔) - 魅力峰值 - 博客园]]