一、哪些情况会导致java程序cpu打满?
在开始排查前,我们先了解可能导致java应用cpu使用率飙升的常见原因:
死循环或无限递归
- 特征:单个线程持续占用一个cpu核心,cpu使用率接近100%。
- 场景:代码逻辑错误、条件判断失误导致循环无法退出。
高复杂度算法处理大量数据
- 特征:cpu使用率随数据量线性或指数级上升。
- 场景:如未优化的排序、递归遍历、大数据计算等。
线程阻塞与唤醒风暴(lock contention)
- 特征:大量线程在
runnable和blocked状态间频繁切换,引发大量上下文切换。 - 场景:锁竞争激烈、synchronized或reentrantlock使用不当。
- 特征:大量线程在
线程池配置不合理
- 特征:线程数过多,导致上下文切换开销巨大,cpu资源被调度消耗。
- 场景:核心线程数、最大线程数设置过大,或队列策略不合理。
频繁gc(垃圾回收)
- 特征:gc线程占用大量cpu,尤其是full gc频繁触发。
- 场景:内存泄漏、堆内存设置不合理、对象创建过快。
此外,还有如正则表达式回溯、jni调用、频繁反射等也可能导致cpu飙升。
二、环境准备:模拟cpu打满场景
为了演示排查过程,我们先编写一段模拟cpu打满的java代码:
// cpu100demoapplication.java
public class cpu100demoapplication {
public static void main(string[] args) {
int corecount = runtime.getruntime().availableprocessors();
system.out.println("cpu核心数:" + corecount);
for (int i = 0; i < corecount; i++) {
new thread(() -> {
while (true) {
// 死循环空转,持续占用cpu
}
}, "cpu-eater-thread-" + i).start();
}
}
}
操作步骤:
# 1. 编译 javac cpu100demoapplication.java # 2. 创建 manifest mkdir -p meta-inf cat > meta-inf/manifest.mf << 'eof' manifest-version: 1.0 main-class: cpu100demoapplication created-by: lyc eof # 3. 打包 jar cvfm cpu100demo.jar meta-inf/manifest.mf cpu100demoapplication.class # 4. 运行测试 java -jar cpu100demo.jar
此时,程序会为每个cpu核心创建一个死循环线程,模拟cpu打满场景。
三、方式一:使用top + jstack定位问题
这是最基础、最经典的排查方式,适用于所有linux环境,无需额外工具。
步骤1:使用top查看进程cpu使用情况
top
观察输出,重点关注:
- pid:进程id
- %cpu:cpu使用率(多核系统下可能超过100%)
- %mem:内存使用率
例如:
pid %cpu %mem command 21423 194.0 10.2 java -jar cpu-demo.jar
说明:我的服务器是2核,因此cpu最大使用率为200%。194%的使用率已接近打满,说明进程 21423 是问题源头。
步骤2:查看进程中各线程的cpu使用情况
使用 top -h 查看线程级cpu占用:
top -h -p 21423
输出中会列出该进程的所有线程,找到cpu使用率最高的几个线程,例如:
pid %cpu command 21444 97.0 java 21445 96.5 java
这两个线程id(21444、21445)极有可能是问题线程。
步骤3:将线程id转换为16进制
jvm线程堆栈中的线程id(nid)是16进制的,需转换:
printf '%x\n' 14898 # 输出:3a32 printf '%x\n' 21445 # 输出:53c5
记住这两个值:3a32 和 53c5。
步骤4:使用jstack查看线程堆栈
执行命令获取进程的线程快照:
jstack -l 21423 > jstack.log
打开日志文件,搜索 cpu-eater
"cpu-eater-thread-0" #14 prio=5 os_prio=0 cpu=390259.69ms elapsed=395.52s tid=0x0000558fec247c50 nid=0x53c2 runnable [0x00007f2288f78000]
java.lang.thread.state: runnable
at cpu100demoapplication.lambda$main$0(cpu100demoapplication.java:8)
at cpu100demoapplication$$lambda$1/0x0000000800c00a08.run(unknown source)
at java.lang.thread.run(java.base@17.0.0.1/thread.java:833)
定位成功! 问题出现在 cpu100demoapplication.java 死循环代码。
四、方式二:使用 arthas 快速诊断
arthas 是阿里巴巴开源的java诊断工具,被誉为“java程序员的瑞士军刀”。它无需修改代码,即可实时监控、诊断线上应用。
1. 安装与启动
# 下载 curl -o https://arthas.aliyun.com/arthas-boot.jar # 启动 java -jar arthas-boot.jar
启动后,arthas会列出当前机器上所有java进程:
* [1]: 3439 org.elasticsearch.bootstrap.elasticsearch [2]: 21423 cpu100demo.jar
输入进程id(如 2),回车进入监控界面。
2. 使用thread命令定位高cpu线程
# 查看cpu使用率最高的5个线程 thread -n 5
输出示例:
"cpu-eater-thread-3" id=17 cpuusage=99.95% deltatime=210ms time=929934ms runnable
at app//cpu100demoapplication.lambda$main$0(cpu100demoapplication.java:8)
at app//cpu100demoapplication$$lambda$1/0x0000000800c00a08.run(unknown source)
at java.base@17.0.0.1/java.lang.thread.run(thread.java:833)
"cpu-eater-thread-4" id=18 cpuusage=99.89% deltatime=210ms time=930931ms runnable
at app//cpu100demoapplication.lambda$main$0(cpu100demoapplication.java:8)
at app//cpu100demoapplication$$lambda$1/0x0000000800c00a08.run(unknown source)
at java.base@17.0.0.1/java.lang.thread.run(thread.java:833)
无需转换进制,直接显示cpu使用率和代码位置,定位问题更直观、更高效!
3. arthas的其他强大功能(扩展)
watch:监控方法的入参、返回值、异常trace:追踪方法调用链,分析性能瓶颈stack:查看指定方法的调用栈dashboard:实时监控系统、jvm、线程、内存等状态jvm:查看jvm信息ognl:执行任意ognl表达式,调用对象方法
arthas让线上问题排查从“盲人摸象”变为“全局掌控”。
五、总结与建议
| 方法 | 优点 | 缺点 | 推荐指数 |
|---|---|---|---|
| top + jstack | 原生工具,无需安装,通用性强 | 操作繁琐,需手动转换进制,信息不够直观 | ⭐⭐⭐⭐ |
| arthas | 功能强大,定位迅速,交互友好,支持热修复 | 需额外安装,有一定学习成本 | ⭐⭐⭐⭐⭐ |
最佳实践建议:
- 日常开发中优先使用arthas,提升排查效率。
- 掌握top + jstack作为基础技能,应对无外网或受限环境。
- 定期监控系统指标,结合prometheus + grafana实现告警。
- 优化代码逻辑,避免死循环、高复杂度算法、过度创建线程。
- 合理配置jvm参数和线程池,避免资源浪费。
结语:
cpu打满并不可怕,关键在于快速定位、精准修复。掌握本文介绍的两种排查方式,你将能在面对线上性能问题时从容不迫,成为团队中不可或缺的技术骨干。
到此这篇关于详解java程序导致cpu打满如何排查的文章就介绍到这了,更多相关java cpu打满内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论