外观
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 javabash
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 5text
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 5text
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 无超时) | 看栈里的驱动类名 |