3.1 核心排查步骤
- 执行
top
命令:查看所有进程占系统CPU的排序。极大可能排第一个的就是咱们的java进程(COMMAND列)。PID那一列就是进程号。(大写P按cpu排序,大写M按照内存排序)比如获取到最占用cpu的进程号为“4261” - 执行
top -Hp 4261
命令:查看java进程下的所有线程占CPU的情况。(大写P按cpu排序,大写M按照内存排序)比如最占用的线程id为"4262" - 执行
printf "%x\n"
4262 命令 :后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制。例如,printf "%x\n 10-》打印:a,那么在jstack中线程号就是0xa. - 执行
jstack 4261 | grep
4262 -A 10 : 查找某进程下-》线程ID(jstack堆栈信息中的nid)=0xa的线程状态。如果"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable
,第一个双引号圈起来的就是线程名,如果是“VM Thread”这就是虚拟机GC回收线程了 - 执行
jstat -gcutil 进程号 统计间隔毫秒 统计次数(缺省代表一致统计)
,查看某进程GC持续变化情况,如果发现返回中FGC很大且一直增大-》确认Full GC! 也可以使用jmap -heap 进程ID
查看一下进程的堆内从是不是要溢出了,特别是老年代内从使用情况一般是达到阈值(具体看垃圾回收器和启动时配置的阈值)就会进程Full GC。 - 执行
jmap -dump:format=b,file=filename 进程ID
,导出某进程下内存heap输出到文件中。可以通过eclipse的mat工具查看内存中有哪些对象比较多
总结:
1、top(获取进程id即pid-按大写P排序后的第一条)
2、top -Hp ${pid}(获取线程id的十进制表示-按“U”的第一条)
3、pirntf "%x\n" (获取线程id的十六进制表示,即nid)
4、jstack -l ${pid}|grep ${nid} -A 10
实践结果如下
3.2 原因分析
1.内存消耗过大,导致Full GC次数过多
执行步骤1-5:
- 多个线程的CPU都超过了100%,通过jstack命令可以看到这些线程主要是垃圾回收线程-》上一节步骤2
- 通过jstat命令监控GC情况,可以看到Full GC次数非常多,并且次数在不断增加。--》上一节步骤5
确定是Full GC,接下来找到具体原因:
- 生成大量的对象,导致内存溢出-》执行步骤6,查看具体内存对象占用情况。
- 内存占用不高,但是Full GC次数还是比较多,此时可能是代码中手动调用 System.gc()导致GC次数过多,这可以通过添加 -XX:+DisableExplicitGC来禁用JVM对显示GC的响应。
2.代码中有大量消耗CPU的操作,导致CPU过高,系统运行缓慢;
执行步骤1-4:在步骤4jstack,可直接定位到代码行。例如某些复杂算法,甚至算法BUG,无限循环递归等等。
3.由于锁使用不当,导致死锁。
执行步骤1-4:如果有死锁,会直接提示。关键字:deadlock.步骤四,会打印出业务死锁的位置。
造成死锁的原因:最典型的就是2个线程互相等待对方持有的锁。
4.随机出现大量线程访问接口缓慢。
代码某个位置有阻塞性的操作,导致该功能调用整体比较耗时,但出现是比较随机的;平时消耗的CPU不多,而且占用的内存也不高。
思路:
首先找到该接口,通过压测工具不断加大访问力度,大量线程将阻塞于该阻塞点。
参考地址
https://blog.csdn.net/a442828032/article/details/95936749(基本同下)
https://zhuanlan.zhihu.com/p/139168280(基本同上)
https://zhuanlan.zhihu.com/p/141536544?from_voters_page=true(有图有真相,详细)