🛠CPU飙高保留现场
# ✅ 方案 A:JFR 持续录制(强烈推荐,低开销)
前提:容器里有 JDK(你能 jps,基本满足)。
1)启动一个 5 分钟环形缓冲的 JFR:
# 以 PID 1 的 Java 进程为例
jcmd 1 JFR.start \
name=SpikeWatch \
settings=profile \
disk=true \
maxage=5m maxsize=128m \
dumponexit=true \
filename=/tmp/omall-jfr.jfr
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- settings=profile:采样器(CPU/热点/锁等),开销很小
- maxage=5m:只保留最近 5 分钟数据(环形缓冲)
- 随时可保留最近窗口
2)当你发现刚刚有 spike 或想要留档时,立刻 dump:
jcmd 1 JFR.dump name=SpikeWatch filename=/tmp/omall-$(date +%s).jfr
1
把文件拷出:
kubectl cp <ns>/<pod>:/tmp/omall-1699999999.jfr ./ # 或 docker cp
1
用 JDK Mission Control 打开 .jfr 就能直接看到最热方法、热点线程、锁竞争、分配等。
小技巧:让它一直跑着就行,几乎不影响性能;出事就 dump 最近 5 分钟。
# ✅ 方案 B:CPU 超阈值时自动抓线程栈(轻量+文本)
有了 procps 后(你已经能 top -p),用下面脚本持续巡检,超过阈值就连抓多次栈:
cat >/usr/local/bin/cpu-spike-dump.sh <<'SH'
#!/bin/sh
PID=${1:-1} # 目标 Java 进程
THRESH=${2:-200} # 触发阈值(%),多核可>100
INTERVAL=2 # 轮询间隔(秒)
OUT=/tmp
echo "[cpu-spike-dump] watch pid=$PID thresh=$THRESH%"
while :; do
CPU=$(ps -p $PID -o %cpu= | awk '{print int($1)}')
[ -z "$CPU" ] && sleep $INTERVAL && continue
if [ "$CPU" -ge "$THRESH" ]; then
TS=$(date +%Y%m%d-%H%M%S)
echo "[cpu-spike-dump] spike $CPU% at $TS, dumping stacks..."
for i in 1 2 3 4 5; do
jcmd $PID Thread.print > "$OUT/thread-$TS-$i.txt" 2>&1 || \
jstack $PID > "$OUT/thread-$TS-$i.txt" 2>&1
sleep 1
done
fi
sleep $INTERVAL
done
SH
chmod +x /usr/local/bin/cpu-spike-dump.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
运行:
/usr/local/bin/cpu-spike-dump.sh 1 200
1
200表示总核为 2 个时占满两个核才触发(按需改)- 触发时会在
/tmp生成thread-*.txt多份,便于比对哪个线程一直热
如果你的 BusyBox
ps不支持-o,就保持你已安装的procps;否则可改为读取/proc/1/stat计算 CPU,但不如上面稳定。
# ✅ 方案 C(可选):临时做一次 60 秒 CPU 火焰图
若允许下载二进制,可用 async-profiler(极低开销采样):
# 假设已把 async-profiler 的 profiler.sh 放到 /opt/aprof/
bash /opt/aprof/profiler.sh -d 60 -e cpu -f /tmp/cpu.svg 1
1
2
2
把 cpu.svg 拷出直接浏览器打开,一眼看到最热栈。
# 用哪个?
- 长期守护:选 方案 A(JFR),一直开着,事后 dump 最近 5 分钟。
- 不想装工具,只要文本证据:选 方案 B,超阈即抓 5 份
Thread.print。 - 要最清晰的热点图:方案 C(需要放入 async-profiler)。
上次更新: 2026/02/28, 09:08:42