java多线程cpu 100%主因是资源争抢、空转或gc,需定位“谁在忙、为什么忙、忙什么”;先用top -hp查lwp,转十六进制后jstack搜索nid,区分空转、锁争、gc三类忙态,并用arthas动态诊断。

Java 多线程并发场景下 CPU 100% 往往不是“某一行代码慢”,而是多个线程在争抢资源、空转或低效协作,导致 CPU 时间片被无效消耗。关键在于定位“谁在忙”“为什么忙”“忙什么”,而不是只看堆栈表象。
先锁定最忙的线程(LWP → nid 对齐)
Linux 系统看到的是轻量级进程 ID(LWP),JVM 线程栈里记录的是十六进制的 native thread ID(nid)。不转换就对不上号,排查会失效。
- 用 top -Hp
查出占用 CPU 最高的线程号(十进制 LWP) - 用 printf "%x\n"
转成十六进制,比如 12345 → 3039 - 用 jstack
> dump.log 导出线程快照,在 dump.log 中搜索 nid=0x3039 - 重点看该线程状态是否为 RUNNABLE,且堆栈停留在某个循环、同步块或正则匹配处
区分三类典型忙态:空转、锁争、GC
高 CPU 线程不等于问题代码本身——它可能是受害者,也可能是加害者。
- 空转型(Busy-wait):如 while(!flag) {}、自旋锁未设超时、无 sleep 的轮询;线程堆栈反复出现在同一行,CPU 占用高但无 I/O 或阻塞
- 锁争型(Lock contention):大量线程卡在 BLOCKED 状态,等待同一个 monitor;jstack 中能看到几十个线程都停在 synchronized 或 ReentrantLock.lock();此时真正占 CPU 的可能只是少数“抢到锁又快速释放”的线程,但整体负载飙升
-
GC 型(GC thread 主导):用 jstat -gc
1000 观察 GC 频率;若 Full GC 或 CMS 并发标记阶段频繁执行,GC 线程本身会吃满 CPU,而业务线程多数处于 STW 等待状态
用 Arthas 快速验证和动态追踪
线上不能改代码、不能重启,Arthas 是最实用的实时诊断工具。
- 启动:java -jar arthas-boot.jar,选目标进程
- 查热点线程:thread -n 5 列出 CPU 消耗 Top 5 线程
- 反编译确认逻辑:jad com.example.Service.doWork 查看实际字节码逻辑(避免 class 与源码不一致)
- 动态追踪方法调用:trace com.example.Service process * --skipJDK 看是否某次调用耗时异常或陷入循环
- 监控锁竞争:thread -b 直接列出当前阻塞在锁上的线程及锁对象
结合代码模式反推常见陷阱
很多 CPU 100% 不是偶然,而是特定并发写法埋下的雷。
- 共享变量未用 volatile 或未加锁,导致线程反复读取过期值并重试(如 CAS 失败后无限重试)
- 使用 Executors.newCachedThreadPool() 处理突发流量,创建数千线程,上下文切换开销压垮 CPU
- 正则表达式含嵌套量词(如 (a+)+),匹配恶意输入时触发灾难性回溯,单线程吃满一个核
- 日志打印中拼接大量字符串("key=" + obj.toString() + ", val=" + ...),尤其在高并发下频繁触发 StringBuilder 扩容和 GC
- ConcurrentHashMap 在高并发 put 时发生哈希冲突+链表转红黑树,若 key 的 hashCode 实现不合理,会退化成 O(n) 遍历
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











