跳到正文
Elaine Blog
返回

CPU 飙高、死锁和长暂停排查

JVM 与性能

CPU 高、应用卡住和长暂停是三类不同症状。排查顺序是先冻结时间范围和系统指标,再采集多份线程证据,最后用 JFR、GC 日志或 Profile 验证原因。重启会清除关键现场,应先完成低影响采集。

Table of contents

Open Table of contents

CPU 飙高:把系统线程对应到 Java 栈

启动 60 秒的CPU 热点样例下载

java -cp target/classes dev.elaine.jvmlab.CpuHotspotSample 60

在另一个终端连续采集三份线程转储和一份 JFR:

jcmd <PID> Thread.print -l > thread-1.txt
jcmd <PID> JFR.start name=incident settings=profile duration=30s filename=incident.jfr
jcmd <PID> Thread.print -l > thread-2.txt
jcmd <PID> Thread.print -l > thread-3.txt

持续处于 RUNNABLE 且多份栈都停在 isPrime 的线程,才是可信热点。单份线程栈只是一张照片,可能恰好拍到正常短任务。

死锁:验证锁等待环

启动死锁样例下载

java -cp target/classes dev.elaine.jvmlab.DeadlockSample
jcmd <PID> Thread.print -l > deadlock.txt

Thread.print 会报告 Java-level deadlock,并列出线程持有和等待的监视器。实验进程不会主动结束,采集后运行 kill <PID>;不要使用模糊的进程名批量终止。

修复策略是统一锁顺序、缩小临界区,或改用带超时的并发结构。增加测试时应使用超时断言,避免 CI 永久挂起。

长暂停:先确定谁暂停了进程

请求延迟突然上升不一定是 GC。可能原因包括:

同时对齐应用延迟、-Xlog:gc*,safepoint、容器 CPU throttling、系统负载和下游指标。若延迟峰值没有对应 GC/Safepoint, 继续找其他层,不要先改 GC 参数。

线上应急最小清单

  1. 记录开始时间、实例、版本、流量和告警指标。
  2. 保存系统 CPU、内存、线程数和容器限制。
  3. 间隔采集至少三份线程转储。
  4. 导出持续 JFR 或启动短时录制。
  5. 保存 GC 与 safepoint 日志。
  6. 评估磁盘和停顿风险后再生成 Heap Dump。
  7. 完成证据采集后执行限流、摘流或重启。

命令和处置人必须进入事件时间线,便于事后区分原始故障与诊断动作造成的影响。

下一步

阅读常量池、字符串与类元数据的内存代价,理解非业务对象如何 扩大长期存活集。


分享这篇文章:

上一篇
内存泄漏、OOM 与 Heap Dump 实战
下一篇
常量池、字符串与类元数据的内存代价