共计 2515 个字符,预计需要花费 7 分钟才能阅读完成。
每个人都必须倒退两种重要的能力:适应扭转与动荡的能力以及为长期指标延缓吃苦的能力。
一、引子
对于互联网公司,线上 CPU 飙升的问题很常见(例如某个流动开始,流量忽然飙升时),依照本文的步骤排查,根本 1 分钟即可搞定!特此整顿排查办法一篇,供大家参考探讨进步。
二、问题复现
线上零碎忽然运行迟缓,CPU 飙升,甚至到 100%,以及 Full GC 次数过多,接着就是各种报警:例如接口超时报警等。此时急需疾速线上排查问题。
三、问题排查
不论什么问题,既然是 CPU 飙升,必定是查一下耗 CPU 的线程,而后看看 GC。
3.1 外围排查步骤
- 执行
top
命令:查看所有过程占零碎 CPU 的排序。极大可能排第一个的就是咱们的 java 过程(COMMAND 列)。PID 那一列就是过程号。 - 执行
top -Hp 过程号
命令:查看 java 过程下的所有线程占 CPU 的状况。 - 执行
printf "%x\n 10
命令:后续查看线程堆栈信息展现的都是十六进制,为了找到咱们的线程堆栈信息,咱们须要把线程号转成 16 进制。例如,printf “%x\n 10-》打印:a,那么在 jstack 中线程号就是 0xa. - 执行
jstack 过程号 | grep 线程 ID
查找某过程下 -》线程 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 工具查看内存中有哪些对象比拟多。
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 不多,而且占用的内存也不高。
思路:
首先找到该接口,通过压测工具一直加大拜访力度,大量线程将阻塞于该阻塞点。
执行步骤 1 -4:
"http-nio-8080-exec-4" #31 daemon prio=5 os_prio=31 tid=0x00007fd08d0fa000 nid=0x6403 waiting on condition [0x00007000033db000]
java.lang.Thread.State: TIMED_WAITING (sleeping)-》期限期待
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.*.user.controller.UserController.detail(UserController.java:18)-》业务代码阻塞点
如上,找到业务代码阻塞点,这里业务代码应用了 TimeUnit.sleep()办法,使线程进入了 TIMED_WAITING(期限期待)状态。
5. 某个线程因为某种原因而进入 WAITING 状态,此时该性能整体不可用,然而无奈复现;
执行步骤 1 -4:jstack 多查问几次,每次距离 30 秒,比照始终停留在 parking 导致的 WAITING 状态的线程。
例如 CountDownLatch 倒计时器,使得相干线程期待 ->AQS->LockSupport.park()。
"Thread-0" #11 prio=5 os_prio=31 tid=0x00007f9de08c7000 nid=0x5603 waiting on condition [0x0000700001f89000]
java.lang.Thread.State: WAITING (parking) -> 无期限期待
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at com.*.SyncTask.lambda$main$0(SyncTask.java:8)-》业务代码阻塞点
at com.*.SyncTask$$Lambda$1/1791741888.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
四、总结
依照 3.1 节的 6 个步骤走下来,根本都能找到问题所在。