JIT耗时优化

举报
程序员进阶 发表于 2024/11/11 18:08:51 2024/11/11
【摘要】 优质博文:IT-BLOG-CN 一、背景业务流量突增,机器直接接入大量流量QPS2000,JIT和GC会消耗太多CPU资源,导致1-2分钟时间内的请求超时导致异常,因此采用流量预热的方式,让机器逐步接入流量,需要预热时长3min。目前服务接入HPA,通过HPA自动扩缩容应用流量变化,当流量激增时,对机器的启动速度带来了挑战,之前通过Swift优化点火时间,已经将机器从容器创建到可接入流量优化...

优质博文:IT-BLOG-CN

一、背景

业务流量突增,机器直接接入大量流量QPS2000JITGC会消耗太多CPU资源,导致1-2分钟时间内的请求超时导致异常,因此采用流量预热的方式,让机器逐步接入流量,需要预热时长3min。目前服务接入HPA,通过HPA自动扩缩容应用流量变化,当流量激增时,对机器的启动速度带来了挑战,之前通过Swift优化点火时间,已经将机器从容器创建到可接入流量优化到2分钟左右,但3min的预热时长成为了应对流量激增的瓶颈,因此优化机器从接入流量到能稳定服务的时长,目标缩减到2min以内。

什么是服务预热: Java应用在刚启动的时候处理相应速度会很慢,只有当热点代码执行了一定次数以后,相应速度才会达到一个稳定状态。由于Java慢启动现象的存在,多数情况下我们有必要对Java应用进行预热,以防止客户端在调用过程中,因为服务器重启或发布事件,而出现大量慢请求。

image.png

流量接入后younggc耗时:峰值900ms左右,最大次数18次

image.png

二、优化思路

名词解释

JIT(Just In Time) 即时编译器: java程序是解释执行的,即运行时将字节码解释为机器码来执行,因此性能差;为了优化Java性能,jvm引入的编译器,随着程序的执行,编译器会将热点代码编译优化为本地代码,来获取更高的执行效率

jvm中集成了两种编译器:
【1】Client Compiler:如C1编译器,注重启动速度和局部的优化,C1的启动速度开,但是峰值性能比C2要差;
【2】Server Compiler:如C2编译器、Graal编译器,关注全局的优化,性能会更好,但由于会进行更多的全局分析,所以启动速度会变慢;
【3】分层编译:为了综合Client ComplierServer Compiler的特性,在启动速度和峰值性能之间取得平衡,java7开始引入分层编译,分为5层:
  ■ 解释执行。
  ■ 执行不带profilingC1代码。
  ■ 执行仅带方法调用次数以及循环回边执行次数profilingC1代码。
  ■ 执行带所有profilingC1代码。
  ■ 执行C2代码。

方法内联: 编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段,JIT大部分的优化都是在内联的基础上进行的;

逃逸分析: 编译器,根据新建对象是否被存入堆中以及是否传入未知代码(未内联代码)中,判断对象是否逃逸,对未逃逸对象进行锁消除、栈上分配优化;

更多内容参考:JIT & AOP

优化思路

【1】通过调整JVM参数,提高JIT效率;
  ● 增加JIT线程;
  ● 调整内联参数,减少内联失败;
  ● 关闭分层编辑,直接进行C2编译;
  ● 关闭逃逸分析,让出资源做其他优化;

【2】更换更新的Server Compiler:Graal编译器使用Java编写,对于Java而言,尤其是新特性,比如Lambda/Stream等更优化。
【3】使用AOT:提前编译,在运行时将Java方法动态编译为本地AOT代码,并将它们存储在共享类缓存中,以此提升启动速度,如:DragonWall/openJ9
【4】业务代码层优化:减少代码量,针对目前基础策略灰度体检,代码体谅大,灰度结束后,代码量减少,JIT应当有所好转。

三、优化过程

优化前机器参数:JIT耗时1.7minGC峰值600ms

image.png

image.png

调整 JVM参数

【1】采用GraalVM编译器: 有效果,但效果没有关闭分层编译好。

-XX:+UnlockExperimentalVMOptions
-XX:+UseJVMCICompiler

image.png

image.png

【2】增加JIT线程数: 默认15个线程

-XX:+CICompilerCountPerCPU=false
-XX:CICompilerCount=16

image.png

image.png

【3】增加内联机器码大小阈值,减少内联失败。同时,增加内联调用次数阈值,延迟内联: 无效果,短暂延迟了JIT耗时峰值;

-XX:+UnlockExperimentalVMOptions
-XX:InlineSmallCode=4000
-XX:InlineFrequencyCount=1000

image.png

image.png

【4】关闭分层编译: 镜像效果明显

-XX:+UnlockExperimentalVMOptions
-XX:-TieredCompilation

image.png

image.png

【5】关闭逃逸分析: 效果不明显,有持续耗时高峰,不可用

-XX:+UnlockExperimentalVMOptions
-XX:-DoEscapeAnalysis

image.png
image.png

AOT

【1】通过openj9AOT替换JIT 启动性能要好一些,但是稳定后吞吐量和延迟都要差一点,然后启动时会有部分超过100ms(大概是首分钟的95线)
【2】使用DragonWall11 不支持JWarmup不可用。

JWarmup:让JVM提前知道哪些方法热的,在处理请求之前就让这些方法提前被编译掉,从而避免了前面边解释,边编译的开销。

代码优化

减少代码量: JIT耗时明显下降。

JVM参数符合使用

【1】采用GraalVM & 关闭分层编译: JIT峰值没有改善,且点火时异常增高。最终项目启动成功的成本100288ms不可用

-XX:+UnlockExperimentalVMOptions
-XX:+UseJVMCICompiler
-XX:-TieredCompilation

image.png

【2】采用GraalVM & 增加内联机器码大小阈值,减少内联失败 & 增加内联调用次数阈值,延迟内联 JTI峰值没有改善,且点火时长异常高。不可用

-XX:+UnlockExperimentalVMOptions
-XX:+UseJVMCICompiler
-XX:+UnlockDiagnosticVMOptions
-XX:InlineSmallCode=4000
-XX:InlineFrequencyCount=1000

image.png

【3】关闭分层编译 & 增加内联机器码大小阈值,减少内联失败 & 增加内联调用次数阈值,延迟内联JTI峰值没有改善,且GC耗时过高。不可用

-XX:+UnlockExperimentalVMOptions
-XX:-TieredCompilation
-XX:+UnlockDiagnosticVMOptions
-XX:InlineSmallCode=4000
-XX:InlineFrequencyCount=1000

image.png
image.png

四、优化结果

【1】JIT-MAXJIT点火耗时Max,从1.9min左右,2月1日关闭分层编译后减少到1.6min左右,代码优化后降到55s左右

image.png

【2】JIT-AVGJIT平均耗时,从原来的10S,2月1日关闭分层编译后减少到7.5s左右,代码优化后降到5s左右

image.png

五、结论

【1】分层编译对JIT耗时有增益效果,但是由于机器差异,对最大耗时的优化不是很明显,从平均耗时看差异较大;
【2】代码重构后,代码量减少,对最大JIT编译耗时优化效果比较明显,平均耗时也有所下降;
【3】优化QPM数据采集准确性,减少由于数据采集延迟带来频繁扩缩容,减少JIT高峰数量;

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。