通过分层编译优化解决JDK性能问题案例分享
发表于 2025/05/22
0
作者|丁浩源
问题背景
在次JDK迁移测试中,从OracleJDK迁移到毕昇JDK,在有些测试用例上,性能出现劣化,记录一次分析定位问题的过程
软件信息
软件名称 |
版本 |
备注 |
毕昇JDK |
1.8.0_442 |
替换oraclejdk 1.8_202, 1.8_291 |
测试数据
有四个用例: 通过自研的java程序测试这四种场景的性能数据,单位s, 表示跑完这4个用例所需的时间。
oraclejdk 1.8_202:
线程数 |
test1 |
test2 |
test3 |
test4 |
80 |
52 |
200 |
70 |
75 |
oracleJDK 1.8.0_291
线程数 |
test1 |
test2 |
test3 | test4 |
80 |
50 |
150 |
61 |
80 |
毕昇JDK:
线程数 |
test1 | test2 |
test3 |
test4 |
80 | 50 |
65 |
45 | 145 |
可以看到前三项测试毕昇JDK性能持平或者领先oracleJDK,第四项测试性能存在落后。
调优分析
1) 整体情况
当前主要test4存在落后测试test4用例的,80并发测试的时候后发现CPU使用较高,达90%左右,整体属于高负载情况。
2) CPU
压测时CPU较高,达90%,为计算密集型测试用例。
3)内存
通过GC日志,可以看到JVM的一些配置信息:
CommandLine flags: -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=32210157568 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
堆内存最大30G,内存充足。
4)GC情况
以下是GC日志:
可以看到GC日志正常,GC时间10ms以下,非常迅速。
5)热点函数
通过perf top 查看运行的热点,发现
主要是JIT的热点,表明程序正在大规模进行编译优化。 这是在预期之内的,因为从用例上看
是执行大规模的循环操作,这些高频热点函数,JVM会进行编译优化,编译成机器码高效执行,避免解释执行。
6)火焰图分析
调优方法
1) 避免编译退优化
当一个方法执行多次后,JVM会对这个方法进行及时编译生成机器码,机器码的执行效率远超解释执行。
过程简化为: 解释执行 -> C1编译 -> C2编译
执行的效率为 C2 > C1 > 解释执行。
当前函数执行到一定的次数会触发C1编译,当C1编译的机器码执行到一定的次数或者循环回边到达一定次数会触发C2编译。
当JVM检查到上次函数的编译不是最优的比如(类型预测失败,锁状态变化等)会触发重新编译,但是默认来讲一个函数有最大的编译次数限制,最大阈值为参数-XX:PerMethodRecompilationCutoff控制,一旦到达这个值,该函数将不在继续编译优化,将会一直使用上一个编译版本进行运行。
措施: JVM设置-XX:PerMethodRecompilationCutoff=-1,表示不限制编译优化次数,可以保证函数一直进行优化,防止出现退优化场景。
调优后数据
线程数 | test1 | test2 | test3 | test4 |
80 | 55 | 120 | 57 | 90 |
2) 关闭分层编译
通过获取java -XX:+PrintFlagsFinal -version | grep TieredCompilation 查看分层编译是否在jdk层面打开,结果发现:
bisheng JDK:
bool TieredCompilation = true {pd product}
oracle JDK:
bool TieredCompilation = false {pd product}
毕昇JDK是按照业界惯例打开的,oracle的该发行版是关闭的。
如果关闭分层编译,那么编译器将跳过C1直接使用C2编译。所编译出来的机器码是优化程度更高的,但代价是资源消耗更高一些。
程序可以通过设置JVM参数:
-XX:-TieredCompilation 关闭分层编译。
调优后数据
线程数 | test1 | test2 | test3 | test4 |
80 | 45 | 91 | 42 | 85 |
调优总结
因为该OracleJDK的分层编译是关闭的,所以性能表现较好,如果将毕昇JDK的分层编译关闭,在该测试用例上性能整体领先oracleJDK。
对于该用例: 可以将分层编译关闭可以大幅提升性能。 但对于正式业务,需要具体测试判断,一般还是建议打开分层编译,可以根据具体业务场景来通过设置-XX:PerMethodRecompilationCutoff=-1,不限制编译次数来提升性能。