一次排查Jvm线程飙升问题的经历

时间:2023-02-23 11:18:08

问题发现

通过线上Grafana监控发现某服务 JVM-Thread线程异常高,并且发现每天都在持续飙升,如下。

一次排查Jvm线程飙升问题的经历


可看到jvm线程少说几百,多则几千甚至过万,并且如果服务不重启,可发现线程数随着时间的推移持续上升,并且没有下降趋势,因此可以看出服务中一定有某些编码没有使用线程池,在持续的new Thread()创建线程,导致线程数飙升。

定位问题

由于线上服务是云部署,在我们公司开发是没有权限登录服务终端查看应用jvm情况,包括线程信息,所以给定位带来的很多困难。

起初我们定位问题方案是通过测试环境开启Spring Actuator监控,通过访问 /actuator/threaddump获取运行时的线程dump信息,相当于直接在终端执行jstack命令获取jvm线程信息。然后在测试环境观察线程数量变化,通过几次的对比去判断哪些线程名在持续飙升,进而定位问题。

但一段时间过后,发现测试环境的线程数量变化并不是很明显,需要等很长一段时间才有可能看到明显的效果。所以放弃治疗。

一次排查Jvm线程飙升问题的经历

无奈。就开始实现二套方案,直接找运维配合导出线上实例机器上的jvm-thread dump文件,然后直接分析thread-dump文件定位问题,文件大致内容如下。

一次排查Jvm线程飙升问题的经历


这里分析的dump文件大致有480多k,如果一行行看起来也比较不清晰,所以直接上工具。

IBM JCA 工具 ​​https://www.ibm.com/support/pages/ibm-thread-and-monitor-dump-analyzer-java-tmda​

载入文件如下:




一次排查Jvm线程飙升问题的经历




从可视化工具可以很清晰看到 Object.wait 等待锁有487个线程,正常情况,一个应用如果某时刻的wait等待锁的线程数量大于几十个就大概率说明服务有问题了,更何况这里出现了几百个,很显然是不正常的,于是我们随便找一个wait状态的线程看下详细信息如下。




一次排查Jvm线程飙升问题的经历






at com.qcloud.cos.http.IdleConnectionMonitorThread.run(IdleConnectionMonitorThread.java:26)




打开IDA源代码如下。




一次排查Jvm线程飙升问题的经历





通过分析,得知在new COSClient()的时候每次new,都会 new 一个IdleConnectionMonitorThread 线程,因此如果使用多例模式每次使用COS Client的时候随着时间的推移就会有大量的空闲线程被创建出来,所以就出现了一开始的问题。UML依赖图如下。




一次排查Jvm线程飙升问题的经历








一次排查Jvm线程飙升问题的经历







一次排查Jvm线程飙升问题的经历







一次排查Jvm线程飙升问题的经历







一次排查Jvm线程飙升问题的经历




解决方案

1.多例模式



每次创建client之后,在使用结束进行手动显示的shutdown掉Cilent客户端。




一次排查Jvm线程飙升问题的经历




2.单例模式



将CosClient 声明到Spring IOC容器中为单利模式,全局使用一个CosClient客户端进行调用。在调用结束之后无需手动进行shutdown调用。例




一次排查Jvm线程飙升问题的经历




附件工具

https://download.csdn.net/download/qianyan0365/15419859