java多线程cpu 100%且线程长期runnable时,大概率存在无退出条件的死循环;需用top定位高cpu线程、jstack查堆栈、结合async-profiler或arthas精准识别自旋热点,并验证volatile可见性、唤醒机制及cas重试逻辑。

Java 多线程中出现 CPU 100% 时,若伴随死循环特征(如线程长时间处于 RUNNABLE 状态、无锁等待、无 I/O 阻塞),往往说明某段代码陷入了无退出条件的循环。定位这类问题,关键不是“制造”死循环,而是通过线上诊断手段快速识别出正在疯狂自旋的线程及其调用栈。
抓取高 CPU 占用的线程快照
先用系统命令定位是哪个 Java 进程占满 CPU:
-
top -H -p
:查看该进程中哪些线程(LWP)CPU 使用率高,记下高占用的线程 ID(十进制) - 将线程 ID 转为十六进制(如
printf "%x\n" 12345),用于后续匹配 - 用 jstack
> thread.log 导出所有线程堆栈,搜索对应十六进制线程 ID(如0x3039)所在线程块
识别典型死循环模式的堆栈特征
真正陷入 CPU 死循环的线程,在 jstack 中通常表现为:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- 状态为
RUNNABLE(不是WAITING或BLOCKED) - 堆栈末尾停留在某个方法内部,且该方法含有 while(true)、while(flag) 但 flag 永不更新、或 for 循环无 break/return 出口
- 常见陷阱位置:轮询未加休眠的 busy-wait(如
while (!ready) {})、同步块外的自旋等待、ConcurrentHashMap computeIfAbsent 内部递归/死循环逻辑、错误的 CAS 自旋重试(未设最大重试次数)
结合代码和运行时上下文交叉验证
仅靠堆栈可能不够,需进一步确认逻辑是否真会卡死:
- 检查该方法是否有 volatile 变量控制循环退出?是否被其他线程修改?
- 是否在 synchronized 块内修改了条件变量,但唤醒机制缺失(如漏掉 notify/notifyAll)?
- 是否用了
AtomicBoolean等原子类,但读写未遵循 happens-before 规则导致可见性失效? - 可在本地复现环境加日志:在循环体内打点(如每千次迭代输出一次计数),观察是否持续打印、是否卡在某次迭代
辅助工具提升定位效率
单靠 jstack 容易遗漏瞬态问题,建议组合使用:
- async-profiler:采样 CPU 火焰图,直接看到热点方法及调用链,比堆栈更直观定位循环入口
- Arthas thread -n 5:一键找出 CPU 使用率最高的前 5 个线程及完整堆栈
-
jstat -gc
和 jmap -histo 排除 GC 频繁或对象暴涨等干扰项,确保确实是计算型瓶颈
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










