Java编译器优化机制
1. 字节码是如何运行的
Java有两种运行模式,分别是:
解释执行:由解释器一行一行翻译执行;
编译执行:把字节码编译成机器码,直接执行机器码
2. 解释执行VS编译执行
解释执行:
优势在于没有编译的等待时间
由于一行一行的去翻译,性能相对就差一些
编译执行:
运行效率会高很多,一般认为比解释执行快一个数量级
带来了额外的开销,比如说内存开销、CPU开销等
3. 查看Java运行模式
使用“java -version”命令查看运行模式,如下所示:
上图中可以看到运行模式mixed mode,mixed mode是混合模型,意思是部分代码解释执行,部分代码编译执行。
-Xint:设置JVM的执行模式为解释执行模式
如果想让springboot项目以解释模式执行,可以使用“java -Xint xxx.jar”就可以了。
-Xcomp:JVM优先以编译模式执行,不能编译的,以解释模式执行
-Xmixed:JVM以混合模式运行,默认就是该模式
一般情况下Java代码一开始由解释器解释执行,当虚拟机发现某个方法或者某个代码块在频繁的运行的时候,就会认为这些代码是“热点代码”,为了提升热点代码的执行效率,会用即时编译器(也就是经常看到的JIT)把这些热点代码编译成与本地平台相关的机器码,并进行各层次的优化。
4. HotSpot的即时编译器
C1编译器:
是一个简单快速的编译器
主要关注局部性的变化
适用于执行时间较短或对启动性能有要求的程序。例如:GUI应用对界面的启动就有一定的要求(IDEA)
也被称为是Client Compiler
C2编译器:
是为长期运行的服务器端应用程序做性能调优的编译器(比如SpringBoot程序)
适用于执行时间较长或对峰值性能有要求的程序
也被称为Server Compiler
5. 分层编译
0级别:解释执行
1级别:简单C1编译,会用C1编译器做一些简单的优化,不开启Profiling(JVM的性能监控)
2级别:受限的C1编译,仅执行带有方法调用次数以及循环回边执行次数Profiling的C1编译
3级别:完全C1编译,会执行所有带有Profiling的C1代码
4级别:使用C2编译器进行优化,该级别会启用一些编译时间较长的优化,一些情况下会根据性能监控信息进行一些非常激进的优化
一般来说级别越高,应用启动越慢,优化的开销越高,峰值性能也越高。默认情况下,JDK8是开启分层编译的,如果只想开启C2,不想使用C1,可以使用下面参数禁止中间编译层(123层):
只开启C2:-XX:-TieredCompilation
只开启C1:-XX:+TieredCompilation -XX:TieredStopAtLevel=1
6. 如何找到热点代码
目前来说业界找到热点代码的思路有以下两种:
基于采样的热点探测:周期检查各个线程的栈顶,如果发现某些方法一直出现在栈顶,说明这个方法是热点方法
基于计数器的热点探测:为每个方法或代码块建立计数器,统计执行的次数,如果超过一定的阈值,就说明是热点代码(HotSpot虚拟机使用的就是这种探测机制)
7. HotSpot内置的两类计数器
方法调用计数器:用于统计方法被调用的次数,在不开启分层编译的情况下,在C1编译器下的默认阈值是1500次,在C2编译器下默认是1000次,可以使用-XX:CompileThreshold=x指定阈值
回边计数器:用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)。在不开启分层编译的情况下,在C1编译器下的默认阈值是13995次,在C2编译器下默认是10700次。可使用-XX:OnStackReplacePercentage=x指定阈值,建立回边计数器的目的主要是为了触发OSR(On-Stack Replacement)编译,参考文档为:
https://www.zhihu.com/question/45910849/answer/100636125
方法调用计数器流程:
如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减,而这段时间就称为此方法统计的半衰周期。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。
回边计数器流程:
8. 总结
本节主要介绍了编译器优化相关的解释运行、编译运行、JVM内置的两款即时编译器、分层编译、热点代码查找内容,本节的概念对于高级开发人员是必须要掌握的基础内容,对于涉及到的JVM参数用时查询即可,本节涉及到的JVM参数如下所示:
参数 |
作用 |
-Xmixed |
混合模式运行(默认方式) |
-Xint |
设置JVM的执行模式为解释执行模式 |
-Xcomp |
JVM优先以编译模式运行,不能编译的,以解释模式运行 |
-XX:-TieredCompilation |
禁止中间层编译 |
-XX:TieredStopAtLevel | 到哪个分层停止 |
-XX:CompileThreshold=X | 指定方法调用计数器阈值(关闭分层编译时才有效) |
-XX:OnStackReplacePercentage=X | 指定回边计数器阈值(关闭分层编译时才有效) |
-XX:-UseCounterDecay | 关闭方法调用计数器热度衰减 |
-XX:CounterHalfLifeTime | 指定方法调用计数器半衰周期(秒) |