CPU高占用
排查思路
- top 命令查看CPU占用率高的进程
- top -H -p ${pid} 命令查看具体是进程的哪个线程占用CPU
- printf ‘%x\n’ ${pid} 将线程的pid转为16进制
- jstack ${十六进制pid} | grep -A 20 查看线程的基本信息与方法调用栈
模拟排查
[root@VM-24-5-centos www]# top
top - 15:39:55 up 69 days, 13:43, 2 users, load average: 3.10, 1.75, 0.93
Tasks: 110 total, 1 running, 109 sleeping, 0 stopped, 0 zombie
%Cpu(s): 50.5 us, 0.5 sy, 0.0 ni, 49.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1881840 total, 100232 free, 829564 used, 952044 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 862992 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
27145 root 20 0 2750780 23504 11488 S 99.7 1.2 1:33.06 java
6493 root 20 0 1033692 51588 16660 S 0.3 2.7 121:18.66 YDService
13842 root 20 0 680292 14520 2464 S 0.3 0.8 10:39.63 barad_agent
PID为27145的java进程占用CPU较多
[root@VM-24-5-centos www]# top -H -p 27145
top - 15:41:10 up 69 days, 13:44, 2 users, load average: 1.60, 1.58, 0.93
Threads: 13 total, 1 running, 12 sleeping, 0 stopped, 0 zombie
%Cpu(s): 50.3 us, 0.2 sy, 0.0 ni, 49.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1881840 total, 103628 free, 826112 used, 952100 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 866456 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
27157 root 20 0 2750780 23504 11488 R 99.7 1.2 2:47.67 java
27145 root 20 0 2750780 23504 11488 S 0.0 1.2 0:00.00 java
27146 root 20 0 2750780 23504 11488 S 0.0 1.2 0:00.10 java
PID为27145的java进程的PID为27157的子线程占用CPU较多
[root@VM-24-5-centos www]# jstack 27145 | grep 0x6a15 -A 20
"Business Thread" #8 prio=5 os_prio=0 tid=0x00007f545c102000 nid=0x6a15 runnable [0x00007f544c6f5000]
java.lang.Thread.State: RUNNABLE
at CPUOccupancyRateTest.lambda$businessThread$0(CPUOccupancyRateTest.java:10)
at CPUOccupancyRateTest$$Lambda$1/834600351.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f545c0b9000 nid=0x6a13 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f545c0b6000 nid=0x6a12 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f545c0b3000 nid=0x6a11 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f545c0b1800 nid=0x6a10 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f545c07e800 nid=0x6a0f in Object.wait() [0x00007f544ccfb000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
[root@VM-24-5-centos www]#
将线程的PID转为16进制后,通过jstack该线程的信息与方法调用栈,该线程的名称为: Business Thread,可定位到执行代码:CPUOccupancyRateTest.java:10。看下具体java代码:
public class CPUOccupancyRateTest {
public static void main(String[] args) {
businessThread();
}
static void businessThread() {
Thread thread = new Thread(() -> {
while (true) { // line 10
// Do nothing
}
});
thread.setName("Business Thread");
thread.start();
}
}
内存高占用(使用MAT分析)
排查思路
若仍未发生OOM:
- top 命令查看Memeory占用率高的java进程
- jmap dump java进程的内存快照文件
- 通过可视化分析工具导入快照文件进行排查
3.1. 占用内存过多的对象是哪些
3.2. 这些对象被谁引用
3.3. 定位到具体代码
注:若堆内存很大时,直接在线上服务器执行 jmap dump 内存快照文件,会导致长时间STW来导出很大的快照文件,引发服务程序不可用。
若发生了OOM,通过配置的JVM启动参数:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=${path}
找到快照文件,导入到可视化分析工具中分析。
OOM模拟排查
JVM启动参数
-Xmx10m -Xms10m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/congqingquan/Desktop
测试代码
public class OOMTest {
public static void main(String[] args) {
oomTest();
}
static void oomTest() {
Thread thread = new Thread(() -> {
List<Person> list = new ArrayList<>();
while (true) {
list.add(new Person());
}
});
thread.setName("OOM Test Thread");
thread.start();
}
static class Person {}
}
使用MAT引入快照文件进行分析
MAT下载地址,红、黄框为常用的分析功能
Histogram
类的实例对象的柱状统计分析图
默认以Byte为单位展示数据,若需要更改显示单位可以在:
Preferences -> Memory Analyzer -> Bytes Display 中选择,一般使用Smart选项。
字段解释:
- Objects:实例对象的数量
- Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
- Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)
Dominator_tree
Dominator Tree(支配树)视图,列出了每个对象实例的引用关系的树状图,包含了占用内存的大小和百分比。
字段解释:
- Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
- Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)
- Percentage:占用堆内存的百分比
Thread_overview
线程概述:程序中所有的线程的信息,如:方法调用栈,方法的栈帧,每个线程、方法中创建的对象以及占用堆内存大小。
字段解释:
- Name:线程名称
- Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
- Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)
- Context Class Loader:上下文类加载器
- Is Daemon:是否为守护线程
回顾排查思路中的第三步,使用排查工具分析:
- 占用内存过多的对象是哪些?Histogram,可以直观的看到统计数据
- 这些对象被谁引用?Dominator_tree,从根对象角度,根据Retained Heap排序,查看哪个GC Root对象的引用关系树中存在的对象占用内存过大
- 定位到具体代码?Thread_overview,从线程角度,根据Retained Heap排序,查看哪个线程生成的对象占用内存过多,分布在线程的哪些方法中。
由于案例比较简单,通过MAT常用功能的讲解,已经可以在解释截图中看到问题根源了:
- Histogram 视图说明了占用内存过多的对象:Person。
- Thread_overview 视图说明了这些对象主要集中在OOM Test Thread线程中。通过线程的方法调用栈,可以看到问题出在oomTest方法上,因为方法内的某个lambda表达式中的一个局部变量ArrayList对象存储了大量的数据。截图中我并没有展开,里面存储的正是大量的Person实例。
Leak Suspects
泄漏疑点:MAT会根据快照文件帮助你分析,并列出分析后的可疑点。点击Detail有更具体的详细说明,非常好用~