tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程

1、cpu疯狂的手段:

1.1、使用jmap指令分享jvm的堆运行情况,看实例了多少个对象

     jmap -histo 11472|grep youx|sort -k 2 -g -r|less

youx : 代表实例对象含有包名(框内代表实例化个数)

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第1张图片

1.2、查看高频使用cpu的线程队列情况 jstack  15543 |grep 3cfc -A 100

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第2张图片

1.3、再实用 jstack  [pid] >> log.log 命令将tomcat进程的jvm日志导出来分析

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第3张图片

可以看到以上有大量的GC线程在等待cpu进行内存的回收,由于以上有大量非GC的线程在等待cpu处理业务,其所实例化的对象就一直内没有被回收从而导致大量的对象处于内存中,因此jvm疯狂的启用GC线程进行内存的回收

a、因此可能会导致系统的cpu飙升从而压垮服务器,导致系统服务无法运行

2、cpu飙升的原因2,因为我们的平台使用mysql-connector-java 链接包使用的是5.1.6,这个jar有一个jdbc连接池无法关闭的bug,从而导致系统有大量的jdbc链接进程在等待关闭,导致系统的负荷也过高,cpu也会出现飙高的现象。

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第4张图片

3、cpu飙高的可能原因3:由于我们平台使用的redis缓存是自己搭建的私有服务,因此存在吞吐量没有优化的可能性,服务在链接redis时,由于在序列化过程redis的吞吐量较低,导致在连接redis的时候,假如有稍大流量的访问涌入的时候,

redis的链接池会被压垮从而导致大量的线程再等等redis的链接资源,从而导致整个系统的阻塞,

因此系统的cpu处于高运行状态,从而造成整个平台的瘫痪

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第5张图片

在分析cpu飙高的过程中,原因2、3的截图显示,所使用的工具是通过jvisualvm (jvm虚拟机自带的工具)进行监控jvm运行的日志,将其导出通过使用eclipse的内存分析工具进行分析日志结果,所分析得出来的可能原因

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第6张图片

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第7张图片

导入jvm远程监控工具上导出的日志

jvm远程监控工具使用方法:https://blog.csdn.net/qq_31854907/article/details/86134137

4、从原因2、3的日志显示由于大量资源在等待资源执行,从而导致大量的任务在等待处理从而大量的线程处于阻塞状态,因此系统的cpu使用也存在飙高的可能性,

由于的平台在使用的过程中,为了方便监控程序的运行过程以及方便依据日志调查系统错误原因,因此会将系统的sql语句答应出来,从而导致大量的进程在竞争log日志数据输出权限,因此直接压垮系统的运行,导致cpu飙高

(有spring 配置日志数据切面,进行日志的统一输出)

5、至此还是没有找出高cpu的根本原因,只知道有dubbo等待的原因,以及大量GC线程的原因,redis优化的原因,但是从排除法出发,使用阿里云redis后,阿里云会将redis配置进行优化,不需要我们管理,从而排除出是redis的原因,而dubbo等待的原因也是系统的cpu已经被其他线程已经占有,导出dubbo存在大量等待的线程,因此cpu飙高的原因也是没有找出来,

后来我们使用jvisualvm 的监控工具发现服务的jvm内存回收出现大量异常情况,

jvisualvm使用方法:https://blog.csdn.net/qq_31854907/article/details/86134137

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第8张图片

查看一些高峰点的内存回收情况,发现在一个小时时间了young GC一个小时触发了800多次,而Full GC也一个小时触发了100来次,于是我们将系统CPU持续飙升的原因归结为是服务可能存在内存泄漏的情况,

于是我们继续排查,使用 jmap 指令查看 系统对象实例化情况是 jmap -histo 11472|grep youx|sort -k 2 -g -r|less

a、发现UserLoginStatusDto这个对象异常的高,回想这个对象存储的信息时用户的登录信息,存储在缓存里面的,

b、而系统有一个拦截是通过前面处理过这个对象的,而且是redis缓存里面获取的数据,处理判断用户请求携带令牌是否正常的处理

c、后来我在线下环境使用登陆场景,我连续点击任何接口请求时发现UserLoginStatusDto是以一个固定的数值在增长,通过查看redis缓存的相关Hash表存储对象的个数刚好是每次增量的两倍,于是我们将目标初步定在可能是redis获取数据的原因

通过代码排查:

1、首先查看入口

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第9张图片

2、继续查看发现,在获取令牌的时候两次调用了redis的接口,这就更进一步,让我将目光聚集在是redis的原因tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第10张图片

3、于是我继续深入redis调查问题,发现这redis对外使用的接口是我们公司jdk开发组,在封装redis原生的时候,对于redis的操作是,每次都会讲redis的表里面的所以数据都会加载到内存中,而我们在实现业务的时候,调用了两次redis,造成了用户每次点击的时候UserLoginStatusDto这个对象在内存中是以增量的形式进行往上增加的,当时我们redis的Hash表中存储的数据是10000多个,才有了第一步的jmap看到的情况,一旦线上的用户达到十万百万级别的话那么UserLoginStatusDto这个对象的实例化的数量就是相当的可怕,拿就会直接造成jvm的内存崩溃

tomcat服务启动后稍有流量cpu疯狂飙高艰辛排查历程_第11张图片

6、至此我也算初步发现了系统内存泄漏的,但是我后来回想,在jvm中局部变量的实例化,是属于即时即用即释放的概念,但是我们的redis获取的数据是属于局部变量,且没有静态化,但是我通过jmap发现UserLoginStatusDto这个是没有被回收的,也就是说没有触发jvm的GC机制。于是我深入了解了jvm发现在jvm中存在,这样一个概念,就是当系统new一个大数组(对象)的情况,或者年轻代的内存空间不够时,会直接在jvm的中老年代分配空间,而老年代的内存区,是需要当内存的使用量达到一定的阈值的时候才触发FULL GC,因此造成第一步的现象UserLoginStatusDto被实例化的现象,因此也就解释了系统内存泄漏的原因

你可能感兴趣的:(JVM)