Skip to content

CPU 100% 定位全过程 ​

Java 进程 CPU 打满是最高频的线上问题。定位链路固定四步,不要凭感觉重启。

标准链路 ​

bash
top -c                                  # ① 哪个 java 进程
top -Hp <pid>                           # ② 进程内哪个线程
printf 'tid hex = %x\n' <tid>           # ③ 转十六进制
jstack <pid> | grep -A 30 'nid=0x<hex>' # ④ 落到代码行

案例一:死循环 / 低效算法 ​

text
top - 16:22:41 up 214 days,  2:11,  1 user,  load average: 8.02, 7.88, 7.21
Tasks: 312 total,   1 running, 311 sleeping
%Cpu(s): 96.8 us,  1.2 sy,  0.0 ni,  1.6 id,  0.0 wa
  PID USER  PR  NI  VIRT  RES   S %CPU %MEM TIME+   COMMAND
21874 app   20   0 8.2g  5.6g  R 396 18.0 912:44  java

解释:us 高、wa 接近 0 → 纯计算型消耗,不是 IO 等待。396% 说明约 4 个核跑满(多核累加)。

text
  PID USER  PR  NI  VIRT  RES   S %CPU TIME+   COMMAND
23104 app   20   0 8.2g  5.6g  R 99.3 312:10   java
23105 app   20   0 8.2g  5.6g  R 98.7 311:55   java
bash
printf '%x\n' 23104     # → 5a40
jstack 21874 | grep -A 12 'nid=0x5a40'
text
"order-pool-3" #152 prio=5 os_prio=0 cpu=18872334.11ms tid=0x00007f3c nid=0x5a40 runnable
   java.lang.Thread.State: RUNNABLE
	at com.bfcube.util.PriceCalculator.expand(PriceCalculator.java:214)
	at com.bfcube.util.PriceCalculator.calc(PriceCalculator.java:96)
	at com.bfcube.svc.OrderService.refreshOrder(OrderService.java:331)

解释:cpu=18872334.11ms 说明这个线程累计烧了 5.2 小时 CPU,明显异常。栈顶落在业务方法而不是 JDK 内部 → 大概率是循环/递归没出口,或正则回溯。

下一步:jstack 连抓 3 次(间隔 5 秒),三次都在 PriceCalculator.java:214 就是它了。

案例二:GC 把 CPU 吃光 ​

bash
jstat -gcutil 21874 1000 5
text
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
100.00   0.00 100.00  99.98  96.10  93.20   9821   1842.11  4412  6120.33  7962.44
100.00   0.00 100.00  99.98  96.10  93.20   9821   1842.11  4412  6120.33  7962.44
  0.00 100.00  99.87  99.99  96.10  93.20   9822   1842.33  4413  6122.87  7964.66

解释:O(老年代)常年 99.98%,FGC 每秒都在涨,且 Full GC 后 O 一点不降——这不是 CPU 问题,是内存泄漏导致的 GC 风暴。CPU 高只是症状。

处置顺序:

bash
jcmd 21874 GC.class_histogram | head -25      # 先看是什么对象占着
jcmd 21874 Thread.print > /tmp/t.txt          # 确认业务线程是否已停摆
jcmd 21874 GC.heap_dump /data/dump/leak.hprof # 抓堆(会 STW,先摘流量)

抓 dump 前先摘流量

堆越大 STW 越久,8G 堆的 heap dump 可能停 30 秒以上,集群里其他节点会跟着超时。正确顺序:先从负载均衡摘掉这个实例 → 抓现场 → 重启恢复。

案例三:线程数爆炸导致上下文切换 ​

bash
vmstat 1 5
text
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
12  0      0 240112  30144 891204    0    0    12   108 9821 48211 22 61 12  5

解释:cs(context switch)每秒 4.8 万次、sy 占 61% —— CPU 高在内核态,不是业务代码。典型原因是线程池开太大(几千个线程互相抢锁)。

bash
ls /proc/21874/task | wc -l                 # 线程总数
jstack 21874 | grep -c 'java.lang.Thread.State: WAITING'   # 多少线程在等
cat /proc/21874/status | grep Threads

处置:把线程池从几百调回 核数 × 2 量级;IO 密集也别超过几百。线程不是越多越好,切换成本是隐形的。

案例四:锁竞争 / 死锁 ​

bash
jstack 21874 | grep -E 'Found .* deadlock' -A 30
jstack 21874 | grep -c 'BLOCKED'

输出:

text
Found one Java-level deadlock:
=============================
"pool-2-thread-1":
  waiting to lock monitor 0x00007f3c12ab (object 0x7c0012340, a java.lang.Object),
  which is held by "pool-2-thread-2"
"pool-2-thread-2":
  waiting to lock monitor 0x00007f3c12cd (object 0x7c0012348, a java.lang.Object),
  which is held by "pool-2-thread-1"

解释:jstack 自带死锁检测,直接给出互相持锁的两个线程和对象地址。大量 BLOCKED 但没有死锁,则是热点锁竞争——找 locked <0x...> 出现次数最多的那个对象。

案例五:外部因素(别只盯着 JVM) ​

bash
pidstat -u 1 -p 21874          # 该进程各线程 CPU 占比(比 top -H 好复制)
perf top -p 21874              # 看热点函数,含 JIT 与内核符号
sar -u 1 5                     # 整机 CPU 趋势,判断是不是"邻居"抢的

解释:容器环境里 %CPU 高可能是宿主机或其他容器导致的干扰;perf top 能看到 [JIT]、G1 Par GC、java.util.regex 这类真实热点,比猜代码快。

速查表 ​

症状组合优先怀疑一条命令验证
us 高 + 栈在业务方法死循环/低效算法/正则回溯jstack 连抓 3 次比对
us 高 + FGC 持续涨GC 风暴(内存泄漏)jstat -gcutil
sy 高 + cs 巨大线程过多/频繁上下文切换ls /proc/pid/task | wc -l
wa 高 + %CPU 不高磁盘瓶颈(不是 CPU 问题)iostat -xz 1 看 %util
大量 BLOCKED锁竞争/死锁jstack | grep -c BLOCKED
单线程 100% 且栈是 socketRead0下游慢(DB/RPC 无超时)看栈里的驱动类名