资讯详情

11-CPU 飙高与死锁排查

📅 2026/9/14 21:36:18 | 华诺云谱 👁 阅读
11-CPU 飙高与死锁排查
本篇是「JVM 与性能调优系列」第 11 篇。负载没变CPU 却跑满接口全超时。top 看是 Java 进程但哪个线程干的死锁更隐蔽——不报错、不崩溃就是所有请求静静卡住。两件事一套排查法。一、CPU 飙高的典型成因先别急着查先分类因为不同成因走不同路径死循环 / 频繁计算某段代码在无出口地跑单线程吃满一个核频繁 GC老年代涨、Full GC 不停多个 GC 线程抢 CPU呼应第 9、10 篇锁竞争激烈大量线程抢同一把锁上下文切换开销暴涨序列化/正则/反射滥用隐性 CPU 消耗大户分类清楚了才能选对工具。下面以最常见的「死循环 频繁 GC」为例走一遍。二、CPU 飙高排查六步这是每个 Java 工程师都该背下来的套路top找到 CPU 最高的进程 PIDtop -Hp PID找到该进程内 CPU 最高的线程 TIDprintf %x\n TID把十进制线程 ID 转成十六进制jstack 里线程号是十六进制jstack PID | grep -A 20 nid用十六进制 nid 定位线程栈看栈里这个线程正在执行什么方法死循环GC业务结合源码定位根因修复关键点第 4 步要快因为线程栈是瞬时的。生产上常用脚本一次抓完或借助 Arthas 一步到位。三、实战死循环导致飙高jstack抓到的栈里如果同一个线程反复出现、停在类似位置business-pool-3 #32 prio5 os_prio0 tid0x... nid0x4f2a runnable at com.xxx.Service.process(Service.java:88) at com.xxx.Service.loop(Service.java:75) // 卡在 while 里且nid0x4f2a正好对应top -Hp里高 CPU 的线程基本锁定第 88 行那个 while 没有退出条件或退出条件永远不满足。修复加边界/超时即可。四、实战频繁 Full GC 导致飙高如果top -Hp里高 CPU 的线程名是GC task thread#0、G1 Concurrent Refinement之类说明CPU 被 GC 吃掉了。路径就回到前面看 GC 日志-Xlog:gc*老年代是不是一直在涨、Full GC 是不是很频繁。这本质是内存问题第 10 篇不是 CPU 问题——别在 CPU 上找错方向。五、死锁排查死锁比飙高更隐蔽进程没崩、CPU 可能还很低但某些请求永远不返回。jstack PID会自动检测并直接打印死锁Found one Java-level deadlock: Thread-A: waiting to lock monitor 0x... (a java.lang.Object) which is held by Thread-B Thread-B: waiting to lock monitor 0x... (a java.lang.Object) which is held by Thread-A含义线程 A 拿着锁 1 等锁 2线程 B 拿着锁 2 等锁 1互相等永远卡住。典型成因synchronized嵌套且获取锁的顺序不一致。修法是所有线程按固定顺序获取锁或改用tryLock(timeout)带超时。六、Arthas 在线神器上面六步要敲好几条命令、还要进制转换生产紧张时容易出错。Arthas 一条命令解决thread -n 3直接列出最忙的 3 个线程及其栈死循环一眼看穿thread -b直接找出死锁的线程比 jstack 更友好dashboard实时看 CPU/内存/线程总览强烈建议把 Arthas 作为线上排查的「第一响应工具」。七、预防锁按固定顺序获取避免交叉嵌套用ReentrantLock.tryLock(timeout)替代无脑synchronized超时可以释放而非死等循环体加退出条件 超时保护别写「理论上会退出」的死循环限流 熔断避免突发流量把 CPU 打满八、锁竞争导致的飙高还有一种常见但隐蔽的 CPU 高没有明显死循环但大量线程卡在lock/Unsafe.park上上下文切换context switch暴增CPU 花在「调度」而非「干活」。top看sy系统态 CPU偏高、vmstat看cscontext switch数值巨大往往就是锁竞争。jstack里大量线程停在waiting to lock同一把锁。解法减小锁粒度拆大锁、用ConcurrentHashMap替代synchronized Map、降低共享可变状态。这本质是并发设计问题不是 JVM 参数能解决的。九、Arthas 实战一条龙把前面六步浓缩成 Arthas 操作# 1. 启动并 attach 到目标进程java-jararthas-boot.jar# 2. 看最忙的 3 个线程死循环直接现形thread-n3# 3. 直接找死锁thread-b# 4. 看全局仪表盘dashboard# 5. 在线抓堆等价于 jmap dumpheapdump /tmp/heap.hprof相比手工top -HpprintfjstackArthas 把进制转换、线程定位、死锁检测全包了生产急救效率提升一个量级。十、一条经验法则CPU 高 线程名带GC→ 内存问题回第 9、10 篇CPU 高 单个业务线程 runnable 卡住 → 死循环六步定位CPU 高 大量线程 waiting to lock → 锁竞争拆锁CPU 不高但请求卡死 → 死锁thread -b先定性、再定量别一上来就瞎改代码。十一、CPU 飙高根因决策表把前面散落的知识点收成一张表遇事对号入座现象可能根因下一步单线程 runnable 卡住死循环jstack 定位代码行线程名带 GC频繁 Full GC看 GC 日志第 9 篇大量 waiting to lock锁竞争拆锁 / 换并发容器线程数暴涨无界线程池改有界线程池案例四CPU 不高但请求卡死锁thread -b 找死锁先定性、再定量别一上来就瞎改代码。十二、容器里 CPU 限额导致的「假飙高」云原生一个坑容器设了cpu limit2但 JVM 启动时的ParallelGCThreads按宿主机的核数比如 32初始化于是 32 个 GC 线程抢 2 个 CPU 配额被 CFS 调度限流GC 反而更慢、业务也饿死表现为 CPU 打满。修复用-XX:ParallelGCThreads显式设成容器配额对应的核数或升级到能感知 cgroup 的 JDK 版本。这提醒我们容器里的 JVM参数要按「容器配额」而非「宿主机」来设。十三、CPU 飙高排查三字诀总结成口诀方便记「看、定、改」。看top 看进程、top -Hp 看线程、jstack/Arthas 看栈定定是死循环、频繁 GC、还是锁竞争用本文决策表改改循环边界、调堆/收集器、拆锁或换并发容器口诀之外最重要的是别慌——CPU 高只是表象顺着工具链一层层剥根因总会现形。总结CPU 飙高先分类死循环 / 频繁 GC / 锁竞争再走「top → top -Hp → printf → jstack」六步定位到代码行死锁靠 jstack 的自动检测或 Arthasthread -b直接抓。记住频繁 GC 引发的 CPU 高根子在内存回看第 9、10 篇别在 CPU 上白费功夫。下一篇我把整套 JVM 调优方法论收个尾。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。