1. 问题情况
在服务器上执行某个任务时,系统突然运行缓慢,top 发现cpu飙升,一度接近100%,最终导致服务假死。
2. 问题排查
1. 执行 “top” 命令:查看所有进程占系统cpu的排序,极大可能排第一的就是自己的java进程,pid就是进程号。
2. 执行 “top -Hp 进程号” 命令:查看java进程下的所有线程占cpu情况。
3. 执行 “printf "%x\n" 10” 命令:后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,需要把线程号转为16进制。例如,printf "%x\n 10-》打印:a,那么在jstack中线程号就是0xa。
4. 执行 “jstack 进程号 | grep 线程id” 命令:查找某进程下-》线程id(jstack堆栈信息中的nid)=0xa的线程状态。如果“"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable”,第一个双引号圈起来的就是线程名,如果是“VM Thread”这就是虚拟机GC回收线程了。
5. 执行 “jstat -gcutil 进程号 统计间隔毫秒 统计次数(缺省代表一致统一)”,查看某进程GC持续变化情况,如果发现返回中FGC很大且一直增大,确认Full GC 也可以使用“jmap -heap 进程ID”,查看一下进程堆内是不是要溢出了,特别是老年代内从使用情况一般是到达阈值(具体看垃圾回收器和启动配置的阈值)就回晋城Full GC。
6. 执行 “jmap -dump:format=b,file=filename 进程ID”,导致某进程下内存heap输出到文件中,可以通过eclipse的mat工具查看内存中有哪些对象比较多。
6-1. jmap
jmap -dump:live,format=b,file=live_web_dump.hprof 22260
3. 原因分析
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不多,而且占用的内存也不高。
思路:
首先找到该接口,通过压测工具不断加大访问力度,大量线程将阻塞于该阻塞点。
5.某个线程由于某种原因而进入WAITING状态,此时该功能整体不可用,但是无法复现;
执行步骤1-4:jstack多查询几次,每次间隔30秒,对比一直停留在parking 导致的WAITING状态的线程。例如CountDownLatch倒计时器,使得相关线程等待->AQS->LockSupport.park()。