每天多学一点点~
话不多说,这就开始吧…
1.前文
最近看了些关于GraalVM的相关文章;学习了在JDK1.8中 HotSpot 虚拟机中,内置了两个 JIT,分别为 C1 编译器和 C2 编译器。知道了一些 c1,c2编译器的相关知识,这里记录下c2编译器的相关bug。
2.C1编译器
C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI 应用对界面启动速度就有一定要求,C1也被称为 Client Compiler。
C1编译器几乎不会对代码进行优化
3.C2编译器
C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性,这种即时编译也被称为Server Compiler。
但是C2代码已超级复杂,无人能维护!所以才会开发Java编写的Graal编译器取代C2(JDK10开始)
4.分层编译
在 Java7之前,需要根据程序的特性来选择对应的 JIT,虚拟机默认采用解释器和其中一个编译器配合工作。
Java7及以后引入了分层编译,这种方式综合了 C1 的启动性能优势和 C2 的峰值性能优势,当然我们也可以通过参数强制指定虚拟机的即时编译模式
在 Java8 中,默认开启分层编译。
通过 java -version 命令行可以直接查看到当前系统使用的编译模式(默认分层编译)
使用“-Xint”参数强制虚拟机运行于只有解释器的编译模式
使用“-Xcomp”强制虚拟机运行于只有 JIT 的编译模式下
5.热点代码和热点探测(JIT)
热点代码,就是那些被频繁调用的代码,比如调用次数很高或者在 for 循环里的那些代码。这些再次编译后的机器码会被缓存起来,以备下次使用,但对于那些执行次数很少的代码来说,这种编译动作就纯属浪费。
JVM提供了一个参数“-XX:ReservedCodeCacheSize”,用来限制 CodeCache 的大小。也就是说,JIT 编译后的代码都会放在 CodeCache 里。
如果这个空间不足,JIT 就无法继续编译,编译执行会变成解释执行,性能会降低一个数量级。同时,JIT 编译器会一直尝试去优化代码,从而造成了 CPU 占用上升。
通过 java -XX:+PrintFlagsFinal –version查询:
热点探测
在 HotSpot 虚拟机中的热点探测是 JIT 优化的条件,热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”
虚拟机为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发 JIT 编译。
方法调用计数器
用于统计方法被调用的次数,方法调用计数器的默认阈值在客户端模式下是 1500 次,在服务端模式下是 10000 次(我们用的都是服务端,java –version查询),可通过 -XX: CompileThreshold 来设定
通过 java -XX:+PrintFlagsFinal –version查询
回边计数器
用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),该值用于计算是否触发 C1 编译的阈值,在不开启分层编译的情况下,在服务端模式下是10700。
怎么算的呢!参考以下公式(有兴趣可了解):
回边计数器阈值 =方法调用计数器阈值(CompileThreshold)×(OSR比率(OnStackReplacePercentage)-解释器监控比率(InterpreterProfilePercentage)/100
其中OnStackReplacePercentage默认值为140,InterpreterProfilePercentage默认值为33,如果都取默认值,那Server模式虚拟机回边计数器的阈值为10700.
回边计数器阈值 =10000×(140-33)=10700
6.C2编译器bug演示
// -Xint 参数强制虚拟机运行于只有解释器的编译模式
public class C2Bug {
public void test() {
int i = 8;
while ((i -= 3) > 0);
System. out .println("i = " + i);
}
public static void main(String[] args) {
C2Bug c2bug = new C2Bug();
for (int i = 0; i < 50_000; i++) {
c2bug.test();
}
}
}
5w次,触发jit及时编译,结果打印错误;
使用-Xint 参数强制虚拟机运行于只有解释器的编译模式,就不会出现问题。
另外把循环次数降低,降低到5000次,就不会触发JIT,就不会触发C2的优化也不会出现问题。
7.结语
今儿先记录这么多。
世上无难事,只怕有心人,每天积累一点点,fighting!!!