JDK调优系列1- 用好性能调优利器:火焰图,事半功倍

举报
RyanPeng 发表于 2021/12/16 15:37:57 2021/12/16
【摘要】 兵欲善其事,必先利其器。程序员在定位性能瓶颈的时候,要是有一个趁手的性能调优工具,能一针见血的指出程序的性能问题,可谓事半功倍。我们常用的性能调优工具Perf(Linux系统原生提供的性能分析工具),能按出现的百分比降序打印CPU正在执行的函数名以及调用栈,如命令:perf recordperf report -n可打印出:这种结果的输出还是不直观的,Linux性能优化大师Brendan G...

兵欲善其事,必先利其器。程序员在定位性能瓶颈的时候,要是有一个趁手的性能调优工具,能一针见血的指出程序的性能问题,可谓事半功倍。

我们常用的性能调优工具PerfLinux系统原生提供的性能分析工具),能按出现的百分比降序打印CPU正在执行的函数名以及调用栈,

如命令:

perf record

perf report -n

可打印出:


这种结果的输出还是不直观的,Linux性能优化大师Brendan Gregg发明了火焰图(因整个图形看起来像燃烧的火焰而得名),以全局的方式来看各个函数的调用时间分布,以图形化的方式列出调用栈。

1      初识火焰图

火焰图是基于perf的结果生成的图形,我们先了解一下怎么去看火焰图。以下图为例:


X轴表示被抽样到的次数。理解X轴的含义,需先了解采样数据的原理。Perf是在指定时间段内,每隔一段时间采集一次数据,被采集到的次数越多,说明该函数的执行总时间长,可能的情况有调用次数多,或者单次执行时间长。因此,X轴的宽度不能简单的认为是运行时长。

Y轴表示调用栈。

如何从火焰图看出性能的瓶颈在哪里?最有理由怀疑的地方,顶层的“平顶”。下面是我们利用火焰图来定位问题的一次实战。

2      火焰图定位问题的实战

2.1      问题场景

问题发生的场景是客户端向服务器发起http请求,服务器返回数据给客户端。客户发现使用OracleJDK 8u_74的性能要远优于OracleJDK 8u_202的性能,图中体现为业务线统计的得到服务器响应的响应时长。

次数

JDK8u74响应时间(单位:秒)

JDK8u202响应时间(单位:秒)

1

0.030

0.834

2

0.036

1.088

3

0.030

0.332

4

0.033

0.597

5

0.018

0.581

6

0.049

0.850

7

0.041

0.355

8

0.021

0.711

9

0.148

0.854

10

0.080

0.754

11

0.025

1.176

12

0.032

0.459

13

0.046

0.443

14

0.025

0.135

15

0.059

0.485

16

0.077

1.093

17

0.123

1.173

18

0.115

0.945

19

0.058

0.384

20

0.035

1.061

平均时间

0.05405

0.7155

 

 

典型的性能问题,202使用CPU的情况是7413倍之多,考虑使用火焰图来定位性能消耗的问题点。

2.2      火焰图定位

对比两张火焰图,使用74ClientHandshaker.processMessage占比为1.15%,而在202中这个函数占比为23.98%,很明显在ClientHandshaker.processMessage带来了性能差异。 

2.3      根因定位

两者在这个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()主要是通过不同的信息类型进行不同的握手消息的处理。而在火焰图中可以看到,74图中,主要消耗在serverFinished()和serverHello()上,而202主要消耗在serverHelloDone()和serverKeyExchange()。

在介绍火焰图的时候,我们有提到,X轴的长度是映射了被采样到的次数。因此需要进一步确定是一直卡在该函数上,还是因为调用频繁。可通过字节码插桩查看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 count5000ms输出一次总执行次数。很明显使用JDK8u202时在不断调用serverHelloDone,74在调用56次后没有再调用过这个函数。

初始化握手时,serverHelloDone方法中,客户端会根据服务端返回加密套件决定加密方式,构造不同的Client Key Exchange消息;服务器如果允许重用该会话,则通过在Server Hello消息中设置相同的会话ID来应答。这样,客户端和服务器就可以利用原有会话的密钥和加密套件,不必重新协商,也就不再走serverHelloDone方法。

从现象来看,OracleJDK8u202没有复用会话,而是建立的新的会话。

2.4      水落石出

查看161的release notes,添加了TLS会话散列和扩展主密钥扩展支持,找到引入的一个还未修复的issue,对于带有身份验证的TLS的客户端,支持UseExtendedMasterSecret会破坏TLS-Session的恢复,导致不使用现有的TLS-Session,而执行新的Handshake。

OracleJDK8u161之后的版本,包括161版本,若复用会话时不能成功恢复Session,而是创建新的会话,会造成较大性能消耗,且积压的大量的不可复用的session造成GC压力变大;如果业务场景存在不变更证书密钥,需要复用会话,且对性能有要求,可通过添加参数-Djdk.tls.useExtendedMasterSecret=false来解决这个问题。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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

举报
请填写举报理由
0/200