使用 perf 解决 JDK8 小版本升级后性能下降的问题【毕昇JDK技术剖析 · 第 1 期】
编者按:在升级 JDK8U 的小版本后(从 8u74 升级到 8u202),遇到性能剧烈下降的问题(性能下降 13 倍)。该应用是一个非常简单的 Web 应用,且应用在 JDK 升级前后并无任何发布修复。通常来说 JDK 小版本升级都是问题修改,不影响功能和性能使用,而应用性能剧烈下降一定是 JDK 的内部 bug。对于这样明确由 JDK 引起的性能问题,该如何解决?最常见的方法是通过工具分析 JVM 执行过程,检查函数执行的情况是否发生变化,如果找到变化,则可以深入分析哪些因素引起了变化,并进一步得到根因。笔者使用 perf 工具分析 JVM 执行时的热点函数,并对出现问题的函数进行剖析,使用函数插桩来分析函数的执行次数,发现不同版本行为差异的根源,并找到了引起问题的根因。希望读者遇到性能问题时可以参照本文使用 perf 工具对问题进行定位。
工欲善其事,必先利其器。程序员在定位性能瓶颈的时候,要是有一个趁手的性能调优工具,能一针见血地指出程序的性能问题,可谓事半功倍。
Linux 中最常用的性能调优工具 Perf(Linux 系统原生提供的性能分析工具),使用 perf 先对应用(假设要采样的应用为 JavaApp)进行采样,使用 record 命令,如下:
perf record java JavaApp
另外 perf 能按出现的百分比降序打印 CPU 正在执行的函数名以及调用栈,如命令:
perf report -n
可打印出:
这种结果的输出还是不直观的,Linux 性能优化大师 Brendan Gregg 发明了火焰图(因整个图形看起来像燃烧的火焰而得名),以全局的方式来看各个函数的调用时间分布,以图形化的方式列出调用栈。
初识火焰图
火焰图是基于 perf 的结果生成的图形,我们先了解一下怎么去看火焰图。以下图为例:
X 轴表示被抽样到的次数。理解 X 轴的含义,需先了解采样数据的原理。Perf 是在指定时间段内,每隔一段时间采集一次数据,被采集到的次数越多,说明该函数的执行总时间长,可能的原因有:调用次数多,或者单次执行时间长。因此,X 轴的宽度不能简单的认为是运行时长。Y 轴表示调用栈。
如何从火焰图看出性能的瓶颈在哪里?最有理由怀疑的地方,顶层的“平顶”。关于 perf 和火焰图使用方法可以参官网http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html。
下面是我们利用火焰图来定位问题的一次实战。
火焰图定位问题的实战
问题场景
问题发生的场景是客户端向服务器发起 http 请求,服务器返回数据给客户端(这是一个非常简单的服务交互)。我们发现使用 JDK 8u74 的性能要远优于 JDK 8u202 的性能,下表中统计了 20 次服务器的响应时长。
从响应时间来看,8u202 相比 8u74 性能下降 13 倍之多,由于应用本身并未做任何修改,所以考虑使用火焰图来定位性能消耗的问题点。在 8u74 和 8u202 分别运行应用,并用 perf 的 record 抓取数据并生成火焰图。
火焰图定位
对比两张火焰图,使用 8u74 时 ClientHandshaker.processMessage 占比为 1.15%,而在 8u202 中这个函数占比为 23.98%,很明显在 ClientHandshaker.processMessage 带来了性能差异。
根因定位
两者在这个 ClientHandshaker.processMessage 上的 cpu 消耗差异很大,继续分析这个函数找到根因。
void processMessage(byte handshakeType, int length) throws IOException { if(this.state >= handshakeType && handshakeType != 0) { //... 异常 } else { label105: switch(handshakeType) { case 0://hello_request this.serverHelloRequest(new HelloRequest(this.input)); break; //... case 2://sever_hello this.serverHello(new ServerHello(this.input, length)); break; case 11:///certificate this.serverCertificate(new CertificateMsg(this.input)); this.serverKey = this.session.getPeerCertificates()[0].getPublicKey(); break; case 12://server_key_exchange 该消息并不是必须的,取决于协商出的key交换算法 //... case 13: //certificate_request 客户端双向验证时需要 //... case 14://server_hello_done this.serverHelloDone(new ServerHelloDone(this.input)); break; case 20://finished this.serverFinished(new Finished(this.protocolVersion, this.input, this.cipherSuite)); } if(this.state < handshakeType) {//握手状态 this.state = handshakeType; } } }
processMessage()主要是通过不同的信息类型进行不同的握手消息的处理。而在火焰图中可以看到,JDK8u74 图中,主要消耗在函数 serverFinished()和 serverHello()上,而 JDK8u202 主要消耗在函数 serverHelloDone()和 serverKeyExchange()。在介绍火焰图的时候,我们有提到,X 轴的长度是映射了被采样到的次数。因此需要进一步确定消耗:函数单次执行耗时过长而成为热点,还是因为频繁调用函数导致函数耗时过长而成为热点。可通过字节码插桩(通过 Instrument 技术实现对函数的计数,然后编译成 agent,执行应用时加载 agent,具体使用 Instrument 的方法可以参考官方文档)查看函数 serverHelloDone()的调用次数及执行时间。
JDK8u202 数据 Execute count : 253 Execute count : 258 Execute count : 649 Execute count : 661 serverHelloDone execute time [1881195 ns] Execute count : 1223 Execute count : 1234 Execute count : 1843 Execute count : 1852 serverHelloDone execute time [1665012 ns] Execute count : 2446 Execute count : 2456 serverHelloDone execute time [1686206 ns] JDK8u74 数据 Execute count : 56 Execute count : 56 Execute count : 56 Execute count : 56 Execute count : 56 Execute count : 56
Execute time 是取了每 1000 次调用的平均值,Execute count 每 5000ms 输出一次总执行次数。很明显使用 JDK8u202 时在不断调用 serverHelloDone,而 74 在调用 56 次后没有再调用过这个函数。
初始化握手时,serverHelloDone 方法中,客户端会根据服务端返回加密套件决定加密方式,构造不同的 Client Key Exchange 消息;服务器如果允许重用该会话,则通过在 Server Hello 消息中设置相同的会话 ID 来应答。这样,客户端和服务器就可以利用原有会话的密钥和加密套件,不必重新协商,也就不再走 serverHelloDone 方法。从现象来看, JDK8u202 没有复用会话,而是建立的新的会话。
水落石出
查看 JDK8u 161 的 release notes,添加了 TLS 会话散列和扩展主密钥扩展支持,找到引入的一个还未修复的 issue,对于带有身份验证的 TLS 的客户端,支持 UseExtendedMasterSecret 会破坏 TLS-Session 的恢复,导致不使用现有的 TLS-Session,而执行新的 Handshake。
JDK8u161 之后的版本(含 JDK8u161),若复用会话时不能成功恢复 Session,而是创建新的会话,会造成较大性能消耗,且积压的大量的不可复用的 session 造成 GC 压力变大;如果业务场景存在不变更证书密钥,需要复用会话,且对性能有要求,可通过添加参数-Djdk.tls.useExtendedMasterSecret=false 来解决这个问题。
后记
如果遇到相关技术问题(包括不限于毕昇 JDK),可以通过毕昇 JDK 社区求助。毕昇 JDK 社区每双周周二举行技术例会,同时有一个技术交流群讨论 GCC、LLVM 和 JDK 等相关编译技术,感兴趣的同学可以添加如下微信小助手入群(请备注:Complier)。
------------------------------------------------------------------------
毕昇JDK技术剖析系列博文:
第1期:使用 perf 解决 JDK8 小版本升级后性能下降的问题
第2期:JVM 锁 bug 导致 G1 GC 挂起问题分析和解决
毕昇JDK资讯:
------------------------------------------------------------------------
本文分享自微信公众号 - openEuler(openEulercommunity)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
谷歌推出新的文本生成图像模型“Muse”,声称性能为同类最佳
自 2021 年初以来,AI 领域推出大量基于文本到图像的模型(例如 DALL-E-2、Stable Diffusion 和 Midjourney 等)。近日,谷歌也公开了一款名为“Muse”的基于文本生成图像的模型,声称可以实现最先进的图像生成性能。 下图均为 Muse 的基于文本生成的图像 一群鱼在海里拼成“MUSE”字样 嘴里叼着“MUSE”牌子的威尔士柯基 带有“Muse”的拿铁咖啡 壁炉中的火焰呈现“MUSE”字样 Muse 在离散标记空间中接受掩蔽建模任务的训练:给定从预训练的大型语言模型 (LLM) 中提取的文本嵌入,训练 Muse 以预测随机掩蔽的图像标记。使用预训练的 LLM 可以实现细粒度的语言理解,转化为高保真图像生成以及对视觉概念(例如对象)的理解,比如空间关系、姿势、基数等。 总体来说,MUSE 的优势在于其 FID 和 CLIP 分数更高、生成效率比其他同类模型快得多,且支持开箱即用的蒙版编辑功能(即支持通过蒙版继续编辑已生成的图片)。 分数更高:MUSE 模型获得了出色的 FID 和 CLIP 分数,可定量衡量图像生成质量、多样性和与文本的对齐情况...
- 下一篇
如何向欧拉操作系统社区提交一个好 PR?
什么是 PR?借用知乎上的一个回答:用类比的方法来解释一下 pull reqeust。想想我们中学考试,老师改卷的场景吧。你做的试卷就像仓库,你的试卷肯定会有很多错误,就相当于程序里的 bug。老师把你的试卷拿过来,相当于先 fork。在你的卷子上做一些修改批注,相当于 git commit。最后把改好的试卷给你,相当于发 pull request,你拿到试卷重新改正错误,相当于 merge。 pull request 简称为 PR,在不同的系统中 PR 有不同的名字,有些系统中使用 MR 即 merged request 来表示 PR。 以 Gitee 为例,一个 PR 由以下几部分:标题、内容以及其评论、提交的代码和文件组成。 为什么说 PR 很重要? 首先,PR 是质量保证体系的基石。因为 PR 是真正合入代码,合入更新的入口,直接影响到项目最终交付件的质量。 其次,PR 是大规模协作开发的基石。欧拉社区的协作是在一个互不相见的“虚拟世界”,PR 几乎是大家交流最重要的通道了。是一种“交流”的语言。 最后,由于社区不会删除任何的 PR,每一个 PR 都记录在历史中,是社区文化的传...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Linux系统CentOS6、CentOS7手动修改IP地址
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Windows10,CentOS7,CentOS8安装Nodejs环境