鲲鹏社区首页
中文
注册
通过分层编译优化解决JDK性能问题案例分享

通过分层编译优化解决JDK性能问题案例分享

javajdk

发表于 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)火焰图分析

通过devkit分别抓取两种JDK运行时候的java进程的火焰图,我们发现毕昇JDK的火焰图执行堆栈和OracleJDK一致,因此执行路径上没有差别,是执行效率的问题,同一个方法的执行比较慢,怀疑是JIT编译优化的差距,oracle JDK使用了更高级别的优化方法,比如直接使用了C2编译。


调优方法

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) 关闭分层编译

及时编译在绝大多数JDK中默认是按分层编译进行的,即:先进行C1编译,运行到一定程度之后再进行C2编译。 C1的CPU耗费较少, 但优化程度较低;C2的CPU耗费较多,但优化程度更高,执行效率更高。
通过获取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


上面两种措施都可以提升该测试场景的性能,措施实施之后,毕昇JDK的整体性能领先OracleJDK。

调优总结

因为该OracleJDK的分层编译是关闭的,所以性能表现较好,如果将毕昇JDK的分层编译关闭,在该测试用例上性能整体领先oracleJDK。

对于该用例: 可以将分层编译关闭可以大幅提升性能。 但对于正式业务,需要具体测试判断,一般还是建议打开分层编译,可以根据具体业务场景来通过设置-XX:PerMethodRecompilationCutoff=-1,不限制编译次数来提升性能。

本页内容