最近测试环境的订单模块经常性的挂掉,查看其CPU使用率达到177%。
CPU占用高一般是由以下情况导致的:
1.代码中存在死循环,导致应用一直占用着cpu。
2.某一张表数据量极大,查出来进行处理极其耗时,这时一般还伴随着内存溢出异常。
3.内存中存在虚拟机无法回收的对象,当内存不足的时候,gc一直在运行,导致CPU使用率飙升。
要确认是由于哪一种情况导致,一般可以按照以下步骤确定。
一. 使用TOP命令,查出是哪一个进程CPU占用率高,如下图所示,13339就是订单模块的进程ID,CPU占用率177.9%。
二. 使用top -H -p13339(13339是Java进程的PID)命令找出了具体的线程资源占用情况(下面这张图是正常的,当时忘记截图保留了,知道此命令即可)
当时占用很高的线程ID是13341,13341,转成十六进制就是0x341d,0x341e。
可以发现是gc线程占用几乎所有的CPU。
三. 执行jstack 13339 > jstack.txt,把堆栈信息导出来,用记事本打开,去匹配0x341d,0x341e
四. 定位到了是gc线程的问题,使用jstat -gcutil 13339 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对象
定位到具体代码,发现每次请求controller的时候都会把OnlineUser,放入到ThreadLocal里面,由于线程池的存在,导致线程一直复用,ThreadLocalMap里面的对象一直无法回收,持续增长,最终导致内存泄露。
至此就找到问题代码了。解决方案就是增加remove操作即可,如下图所示:
修改完成之后的内存快照如下,已经看不到线程对象,说明修改生效了。