最近测试环境的订单模块经常性的挂掉,查看其CPU使用率达到177%。
CPU占用高一般是由以下情况导致的:
- 代码中存在死循环,导致应用一直占用着cpu。
- 某一张表数据量极大,查出来进行处理极其耗时,这时一般还伴随着内存溢出异常。
- 内存中存在虚拟机无法回收的对象,当内存不足的时候,gc一直在运行,导致CPU使用率飙升。
要确认是由于哪一种情况导致,一般可以按照以下步骤确定。
一. 使用TOP命令,查出是哪一个进程CPU占用率高,如下图所示,13339就是订单模块的进程ID,CPU占用率177.9%,
二. 使用top -H -p13339(13339是Java进程的PID)命令找出了具体的线程资源占用情况(下面这张图是正常的,当时忘记截图保留了,知道此命令即可)
当时占用很高的线程ID是13341,13341,转成十六进制就是0x341d,0x341e
三. 执行jstack 13339 > jstack.txt,把队长信息导出来,用记事本打开,去匹配0x341d,0x341e
可以发现是gc线程占用了CPU。
四. 定位到了四gc的问题,使用jstat -gcutil 10206 2000 50命令查看GC的回收情况
E(Eden区)跟O(Old区)的内存已经被耗尽了,FGC(Full GC)的次数高达4447次,FGCT(Full GC Time)的时间高达3986秒,即平均每次FGC的时间为:3986/4447≈ 0.89秒。每隔一秒就进行一次GC操作,也就是说,Java进程都把时间花在GC上了,所以就没有时间来处理其他事情。
此时去查看订单模块的日志,发现内存溢出的情况:
20190827 17:31:41.473 [DiscoveryClient-HeartbeatExecutor-0] ERROR com.netflix.discovery.DiscoveryClient.renew():824 - DiscoveryClient-ORDER/order-200-186:order:8023 - was unable to send heartbeat!
java.lang.OutOfMemoryError: Java heap space
五.到这里,基本可以确定,是由于内存中存在虚拟机无法回收的对象,当内存不足的时候,gc一直在运行,导致CPU使用率飙升。但是是哪些对象不能回收呢,这就需要导出当时的内存快照dump文件,使用jmap -dump:file=du.dump 13339,把导出的du.dump用JProfiler打开(需要把dump后缀改成hprof才能用JProfiler打开),结果如下图:
只从这张图仅仅可以看出前两个对象大小只占总大小的9%,其他什么也看不出来,所以需要有一份对比数据,这时我重启了订单模块,订单模块恢复正常,这时重新导出dump文件,这时的结果如下:
通过对比发现,正常的dump文件前两个对象占用到了37%,异常的dump文件前两个对象只占用了9%,这说明异常的dump文件有其他大文件稀释了这个比例,通过仔细对比之后,发现异常的dump文件包含很多重复的对象
查看这些相同对象里面的内容发现是存在ThreadLocal中的User对象,定位到具体代码,发现,在使用完成之后没有手动remove,导致对象越来越多,在某些情况下,ThreadLocal里面的对象,gc是没法回收的。至此就找到问题代码了。解决方案就是增加remove操作即可。