1、查看线上机器cpu情况及进程id
登录线上机器,通过top查看进程占用内存和cpu的情况
不出意外,如图所示,四核机器cpu全被java程序占用了
2、查看java进程资源占用情况
具体是进程内的哪些线程占用的呢?用 top -H -p6902 (6902是Java进程的PID)命令找出具体的线程资源占用情况,如下图四所示:
图中的PID为Java线程的id,可以看到id为6904、6905、6906、6907这四个线程基本把CPU资源全部吃完了
3、通过jstack查看具体线程的堆栈信息
用命令 jstack 6902 > jstack.txt (6902是Java进程的PID)打印Java进程的堆栈信息放到jstack.txt文件;由于堆栈打印的线程的native id是十六机制的,所以需要把十进制的线程id(6904、6905、6906、6907)转化成十六进制(0x1af8、0x1af9、0x1afa、0x1afb);最后,通过 cat jstack.txt | grep -C 20 0x1af8 命令找到了具体的线程信息,如下图所示:
通过上图可以发现,把CPU占满的线程是GC的线程,Java的垃圾回收把CPU的资源耗尽了
4、查看GC情况
使用命令 jstat -gcutil 6902 2000 10 (6902是Java进程的PID)来观察GC的运行信息,如下图所示:
通过上图可以知道,E(Eden区)跟O(Old区)的内存已经被耗尽了,FGC(Full GC)的次数高达6989次,FGCT(Full GC Time)的时间高达36453秒,即平均每次FGC的时间为:36453/6989 ≈ 5.21秒。也就是说,Java进程都把时间花在GC上了,所以就没有时间来处理其他事情。所以基本可以确定是内存泄漏的问题。
5、查看堆内存中对象占用情况
如何确定是哪些代码导致的这个问题呢?这时候就可以使用jmap查看Java的内存占用信息。jmap是JDK内置的内存映射工具,位于JDK根目录的bin文件夹下面,可用于获取java进程的内存映射信息。通过命令 jmap -histo 6902 (6902是Java进程的PID)打印出了Java的内存占用信息,如下图所示:
由图可以得到,占用内存资源的TOP10类([C 是指char[],String类内部使用char[]来保存数据)的名称、实例数以及占用内存大小(单位:byte),于是问题排查就变得非常简单了。最后,通过review代码确定了问题所在:
- 部分接口使用到了L5QOSPacket这个L5的工具类没有做单例,每次请求接口都会生成一个新的实例,浪费了大量的内存。
- 代码里边用到的一个第三方提供的QcClient客户端存在内存泄露问题,代码中不恰当地new了大量的对象,而且对存储在ConcurrentHashMap的数据没有做清除清理,从而导致数据一直累计,内存占用持续增加。
至此,问题已经定位完成。