JVM的编译方式及运行效率
Q:JVM采用了什么编译方式?
A:JVM的编译方式分为前端编译和后端编译,部分静态提前编译(AOT)
前端编译
简介
由于JVM的输入为字节码(.class文件),而我们的编程输出的是自然语言(.java文件),所以就需要一种将我们的输出(.java文件)转换成JVM输入(.class文件)的编译工具,它就是前端编译。
常见的前端编译即为JDK自带的Oracle的javac
工具
过程
常见的javac编译过程大概分为了四个阶段。
第一阶段:词法分析。
第二阶段:语法分析。
第三阶段:语义分析。
第四阶段:字节码生成。
后端编译
从硬件视角上来看,JAVA字节码(.class)无法直接被底层硬件执行,需要不同平台的虚拟机给编译成各自平台适用的机器码。这个编译过程便是JVM的最大的功能之一。也正是这个功能保证了JAVA语言的跨平台的特性。
- 解释执行
顾名思义,就是逐条的将字节码翻译成机器码并执行。比如成同声传译,边听别写,解释执行便是边解释边执行。
- JIT
即时编译(Just In Time compilation),指在运行时,JVM将“热点方法”(class字节码)全部编译成机器码后在执行。
- 混合模式
类型 | 优点 | 缺点 |
---|---|---|
解释执行 | 无需等待 | 执行速度较慢 |
JIT | 执行效率较高 (程序执行符合二八定律) | 1、搜集监控信息影响程序运行 2、编译程序信息占用程序运行时间(例如程序启动变慢) 3、编译机器码占用内存 |
JVM结合两者的优缺点,综合了两种编译方式的优点,采用了混合模式。HotSpot默认采用了混合模式,它先解释执行字节码,然后将其中反复执行的代码,以方法为单位进行及时编译。
静态提前编译(AOT)
在程序执行前,提前将java代码翻译成本地代码,一边在程序运行时直接调用本地代码。
AOT 编译器从编译质量上来看,肯定比不上 JIT 编译器。其存在的目的在于避免 JIT 编译器的运行时性能消耗或内存消耗,或者避免解释程序的早期性能开销。
在运行速度上来说,AOT 编译器编译出来的代码比 JIT 编译出来的慢,但是比解释执行的快。而编译时间上,AOT 也是一个始终的速度。所以说,AOT 编译器的存在是 JVM 牺牲质量换取性能的一种策略。就如 JVM 其运行模式中选择 Mixed 混合模式一样,使用 C1 编译模式只进行简单的优化,而 C2 编译模式则进行较为激进的优化。充分利用两种模式的优点,从而达到最优的运行效率。
Q:JVM的运行效率
A:HotSpot充分利用了各编译方式的优缺点,使JVM的运行效率达到最高。
上图是默认和使用命令调用不同的编译模式
对程序来说,也符合二八定律,即百分之二十的代码占用了百分之八十的计算资源。对于大部分的不常用的代码,我们我们无需耗费资源对其进行编译处理,只需要在执行时采用解释执行的方式即可;而对于百分之二十的常用的代码,我们可以采用JIT的方式,将其即时编译成机器码,这样在执行时的效率就能达到最高。
即时编译的运行效率,理论上可以超过静态编译的,相对于静态编译,即时编译可以保存一些运行时信息,(例如接口下的实现类,静态编译需要遍历所有的实现类来找到目标类,而即时编译所对应的就是对应的实现类)
C1、C2、Graal编译器
在采用即时编译的策略上,HotSpot也分为不同的做法,分别为较为缓和的C1编译器和较为激进的C2编译器,以及在JDK10中被引入的实验性质的Graal编译器。
引入了多个编译器,便是应对更加复杂的程序,在运行时间和运行效率中做取舍。
C1编译器又被称为Client编译器,编译时间相对较短,优化比较简单,常见的是启动的更快。
C2编译器又被称为Server编译器,编译时间相对较长,优化方式比较复杂和全面,优化后的代码执行效率更高。
C2的优化对比C1体现在两方面,一方面是进行了更多更全面的优化,另一方面是面对同一种优化采用不同的算法。
分层编译
从JDK7开始,HotSpot默认采用了分层编译的方式,“热点方法”会先被C1编译,“热点方法”的“热点”会被C2编译。
达到热点代码是通过热点算法来进行统计的,而分层编译的热点阈值又不同默认是的达到2k进入C1,达到15k进入C2(
C1>2k, C2>15k
)。热点代码有两种算法,基于
采样
的热点探测和基于计数器
的热点探测。一般采用的都是基于计数器的热点探测。基于计数器的热点探测又有两个计数器,方法调用计数器(热度衰减机制),回边计数器(OSRP计算公式 OnStackReplacePercentage),他们在C1和C2又有不同的阈值。
在运行时,解释执行和即时编译会同时进行,编译完成后的机器码会在下次执行时替代解释执行。
HotSpot 的即时编译是放在额外的编译线程中进行的。HotSpot 会根据 CPU
的数量设置编译线程的数目,并且按 1:2
的比例配置给 C1
及 C2
编译器。
参考
- 《极客时间-深入拆解虚拟机-郑雨迪》
- Java三种编译方式 - CSDN
- HotSpot原理指南-个人博客
- hotspot热点代码探测-知乎