在Java编译原理我们已经讲述了Java编译中的前端编译(javac),今天我们就看一下后端编译。
1. 为什么需要后端编译(JIT)
当我们的字节码文件被虚拟机加载过后,其实就可以解释执行,也就是说即使没有后端编译我们的Java程序也可以运行。之所以需要后端编译是因为解释执行虽然会提高程序的启动速度,但当程序稳定运行以后,运行速度不会再有所改善。计算机的世界与天下武功一样,唯快不破,为了不断提高我们的程序运行速度,我们需要在程序运行过程中对特定代码进行编译,将本地代码编译成机器可以直接识别的机器代码。
2. HotSpot虚拟机的JIT
HotSpot VM中集成了两种编译器,Client Compiler和Server Compiler,它们的作用也不同。Client Compiler注重启动速度和局部的优化,Server Compiler则更加关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢。两种编译器有着不同的应用场景,在虚拟机中同时发挥作用。
2.1 Client Compiler
HotSpot VM中带有一个Client Compiler C1编译器,它主要做以下事情:
2.2 Server Compiler
Server Compiler主要关注一些编译耗时较长的全局优化,甚至会还会根据程序运行的信息进行一些不可靠的激进优化。这种编译器的启动时间长,适用于长时间运行的后台程序,它的性能通常比Client Compiler高30%以上。目前,Hotspot虚拟机中使用的Server Compiler有两种:C2和Graal。
关于Client Compiler和Server Compiler后面我会单独写一下具体的编译过程。
3. 分层编译
即时编译器编译代码需要时间,一般编译出优化程度更高的代码(影响程序启动响应速度,但是会提高运行效率),编译会花费更多的时间。为了在程序启动响应速度和运行效率之间达到平衡,HotSpot虚拟机采用分层编译,分层编译的思想将JVM的执行状态划分为5层:
3.1 编译条件
在程序运行过程中,热点代码会触发编译,热点代码有以下两类:
热点代码通过热点探测的方式进行判定,判定的方式大约有两种:
Hotspot虚拟机采用的是基于计数器的热点探测,虚拟机为每个方法准备了两类计数器:
在分层编译开启的情况下,触发编译由以下条件来判断:
以上两个条件满足其中一个即可触发即时编译,系数会由虚拟机根据当前编译的方法数以及编译线程数动态调整。
公式如下:
// i为方法调用计数器记录的方法调用次数
// b为回边计数器记录的循环回边次数
i > TierXInvocationThreshold * s || (i > TierXMINInvocationThreshold * s && i + b > TierXCompileThreshold * s)