生产环境中,经常会遇到CPU持续飙高或内存、IO飙高,如何快速定位问题点是很多新手头疼的问题,只能通过经验和代码推理,其实这里针对Java程序可以通过top和jstack命令,快速定位到问题代码。
Top命令的输出
具体定位之前,先补全一下top命令的输出解释,top 命令的输出是动态更新的,通常每3秒刷新一次,但这个刷新频率可以通过交互命令 d
来调整。看一个top命令的输出样例:
top - 16:45:26 up 5 days, 3:12, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 239 total, 1 running, 238 sleeping, 0 stopped, 0 zombie
%Cpu(s): 1.0 us, 0.6 sy, 0.0 ni, 98.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 16280104 total, 10346652 free, 2689256 used, 3256196 buff/cache
KiB Swap: 2097148 total, 2097148 free, 0 used. 7256332 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 user1 20 0 12345672 1048576 524288 S 0.7 6.4 2:34.16 java
1 root 20 0 0 0 0 S 0.0 0.0 0:03.16 init
72 www-data 20 0 344736 8192 7168 S 0.3 0.1 0:01.93 apache2
top
命令的输出分为两部分:顶部的系统概览和底部的进程列表。
顶部的系统概览
- 任务:显示系统中的进程总数,以及运行(R)、休眠(S)、停止(T)、僵尸(Z)状态的进程数量。
- %Cpu(s):CPU使用率的百分比,通常分为用户空间(us)、系统空间(sy)、空闲(id)、等待I/O(wa)和其他(hi、si、st)。
- KiB Mem:系统内存的使用情况,包括已使用(used)、空闲(free)、缓存(buffers/cached)和可用内存(available)。
- KiB Swap:交换空间的使用情况,包括已使用(used)和空闲(free)。
- 时间:显示当前时间、系统运行时间、平均负载(1分钟、5分钟、15分钟)。
底部的进程列表
- PID:进程ID。
- USER:进程所有者的用户名称。
- PR:进程的优先级(数值越小,优先级越高)。
- NI:进程的nice值(影响进程的优先级)。
- VIRT:虚拟内存的大小,单位通常是KiB。
- RES:常驻内存的大小,即进程实际占用的物理内存,单位通常是KiB。
- SHR:共享内存的大小,单位通常是KiB。
- S:进程的状态,如休眠(S)、运行(R)、停止(T)等。
- %CPU:进程占用的CPU使用率百分比。
- %MEM:进程占用的内存使用率百分比。
- TIME+:进程占用CPU的总时间。
- COMMAND:启动进程的命令名称或命令行。
在这个示例中,系统已经运行了5天3小时12分钟,平均负载在过去1分钟是0.00,过去5分钟是0.01,过去15分钟是0.05。CPU使用情况显示1%的用户空间使用率和0.6%的系统空间使用率,其余为空闲。内存方面,总共有16280104KiB的内存,其中10346652KiB是空闲的,2689256KiB被使用,3256196KiB作为缓存或缓冲。交换空间总共有2097148KiB,全部空闲。
进程列表显示了PID为1234的进程占用了较多的CPU和内存资源,它是以用户 user1
运行的,它是一个Java程序。
快读定位源代码步骤
通过top可以快速定位到资源利用异常的进程程序,这时候,如果是我们开发的Java程序占用较高的资源,但是又无法确认是具体哪一部分占用时,则可以通过 top命令和jstack进行具体代码的定位。思路如下:
首先,使用Top命令,发现存在问题的进程:
top - 10:05:00 up 3 days, 5:25, 1 user, load average: 0.10, 0.15, 0.20
Tasks: 242 total, 2 running, 240 sleeping, 0 stopped, 0 zombie
%Cpu(s): 5.0 us, 1.0 sy, 0.0 ni, 94.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 16384M total, 8192M used, 8192M free, 2048M buff/cache
KiB Swap: 4096M total, 0M used, 4096M free. 4096M avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
23456 root 20 0 21G 15G 1024K S 0.5 91.5 1:50.01 java
73455 root 20 0 0.2G 0.1G 1024K S 0.5 91.5 00:34.01 mysqld
11234 root 20 0 0.1G 0.15G 1024K S 0.5 91.5 1:45.03 sh
在这个例子中,PID为23456的 Java 进程使用了超过15GB的物理内存,并且占用了91.5%的内存资源。这可能意味着它正在消耗大量内存,可能会导致内存泄漏或其他内存使用问题。
使用Top查看进程里的线程
获得了具体的问题进程后,通过 top -Hp [PID] 来查看其内部线程
top -Hp 23456
top - 10:15:00 up 3 days, 5:35, 1 user, load average: 0.50, 0.40, 0.30
Tasks: 500 total, 2 running, 498 sleeping, 0 stopped, 0 zombie
%Cpu(s): 10.0 us, 2.0 sy, 0.0 ni, 88.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 16384M total, 9000M used, 7384M free, 2048M buff/cache
KiB Swap: 4096M total, 0M used, 4096M free. 7384M avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
23457 root 20 0 21G 15G 1024K S 5.0 91.5 1:50.01 java
23457 root 20 0 4G 3.5G 8192K R 10.0 2.1 0:05.20 java
23458 root 20 0 4G 3.5G 8192K S 3.0 2.1 0:04.30 java
23459 root 20 0 4G 3.5G 8192K S 2.0 2.1 0:03.40 java
将线程ID转为16进制
找到对应异常的线程,使用16进制转换命令 printf "0x%x" [线程ID],获取其线程16进制编码
[root@10]# printf "0x%x" 23457
0x5ba1[root@10]#
jstack跟踪堆栈定位代码
拿到了线程16进制编码,则通过java工具内置的jstack命令进行代码定位:
jstack [PID] | grep [线程16进制编码] -A 5
# 数字5表示输出栈顶最前几行代码
jstack 23456 | grep 0x5ba1 -A 5
"Thread-3" #23458 prio=5 os_prio=31 cpu=33.42ms elapsed=6.25s tid=0x5BEA runnable
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.get(HashMap.java:628)
at com.example.MyApplication.doSomething(MyApplication.java:42)
at com.example.MyApplication.access$000(MyApplication.java:18)
at com.example.MyApplication$1.run(MyApplication.java:77)
at java.lang.Thread.run(Thread.java:748)