CPU、内存占用率高排查

CPU高占用

排查思路

  1. top 命令查看CPU占用率高的进程
  2. top -H -p ${pid} 命令查看具体是进程的哪个线程占用CPU
  3. printf ‘%x\n’ ${pid} 将线程的pid转为16进制
  4. jstack 进程pid grep -A 20  ${十六进制线程pid}  查看线程的基本信息与方法调用栈 16进制 0x开头

模拟排查

[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

  1. top 命令查看Memeory占用率高的java进程
  2. jmap dump java进程的内存快照文件
  3. 通过可视化分析工具导入快照文件进行排查
    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 {}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

使用MAT引入快照文件进行分析

MAT下载地址,红、黄框为常用的分析功能

在这里插入图片描述

Histogram

类的实例对象的柱状统计分析图

在这里插入图片描述

默认以Byte为单位展示数据,若需要更改显示单位可以在:
Preferences -> Memory Analyzer -> Bytes Display 中选择,一般使用Smart选项。

字段解释:

  1. Objects:实例对象的数量
  2. Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
  3. Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)

Dominator_tree

在这里插入图片描述

Dominator Tree(支配树)视图,列出了每个对象实例的引用关系的树状图,包含了占用内存的大小和百分比。

字段解释:

  1. Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
  2. Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)
  3. Percentage:占用堆内存的百分比

Thread_overview

在这里插入图片描述

线程概述:程序中所有的线程的信息,如:方法调用栈,方法的栈帧,每个线程、方法中创建的对象以及占用堆内存大小。

字段解释:

  1. Name:线程名称
  2. Shallow Heap:实例对象占用的内存大小(仅单纯的将每个实例的大小相加汇总)
  3. Retained Heap:实例对象若都可以被回收,那么可以被GC释放的内存大小(不仅需要考虑每个实例本身的大小,也会计算实例成员属性指向的实例的大小)
  4. Context Class Loader:上下文类加载器
  5. Is Daemon:是否为守护线程

回顾排查思路中的第三步,使用排查工具分析:

  1. 占用内存过多的对象是哪些?Histogram,可以直观的看到统计数据
  2. 这些对象被谁引用?Dominator_tree,从根对象角度,根据Retained Heap排序,查看哪个GC Root对象的引用关系树中存在的对象占用内存过大
  3. 定位到具体代码?Thread_overview,从线程角度,根据Retained Heap排序,查看哪个线程生成的对象占用内存过多,分布在线程的哪些方法中。

由于案例比较简单,通过MAT常用功能的讲解,已经可以在解释截图中看到问题根源了:

  1. Histogram 视图说明了占用内存过多的对象:Person。
  2. Thread_overview 视图说明了这些对象主要集中在OOM Test Thread线程中。通过线程的方法调用栈,可以看到问题出在oomTest方法上,因为方法内的某个lambda表达式中的一个局部变量ArrayList对象存储了大量的数据。截图中我并没有展开,里面存储的正是大量的Person实例。

Leak Suspects

在这里插入图片描述

泄漏疑点:MAT会根据快照文件帮助你分析,并列出分析后的可疑点。点击Detail有更具体的详细说明,非常好用~

posted @ 2023-05-06 09:31  甜菜波波  阅读(145)  评论(0编辑  收藏  举报