详解Java程序导致CPU打满如何排查

 更新时间:2025年12月04日 10:35:25   作者:Freed_在路上  
本文主要介绍了Java程序CPU打满的常见原因和排查方法,常见的原因包括死循环、高复杂度算法、线程阻塞与唤醒风暴、线程池配置不合理、频繁GC等,下面就来详细的介绍一下,感兴趣的可以了解一下

一、哪些情况会导致Java程序CPU打满?

在开始排查前,我们先了解可能导致Java应用CPU使用率飙升的常见原因:

  1. 死循环或无限递归

    • 特征:单个线程持续占用一个CPU核心,CPU使用率接近100%。
    • 场景:代码逻辑错误、条件判断失误导致循环无法退出。
  2. 高复杂度算法处理大量数据

    • 特征:CPU使用率随数据量线性或指数级上升。
    • 场景:如未优化的排序、递归遍历、大数据计算等。
  3. 线程阻塞与唤醒风暴(Lock Contention)

    • 特征:大量线程在 RUNNABLEBLOCKED 状态间频繁切换,引发大量上下文切换。
    • 场景:锁竞争激烈、synchronized或ReentrantLock使用不当。
  4. 线程池配置不合理

    • 特征:线程数过多,导致上下文切换开销巨大,CPU资源被调度消耗。
    • 场景:核心线程数、最大线程数设置过大,或队列策略不合理。
  5. 频繁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

记住这两个值:3a3253c5

步骤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功能强大,定位迅速,交互友好,支持热修复需额外安装,有一定学习成本⭐⭐⭐⭐⭐

最佳实践建议:

  1. 日常开发中优先使用Arthas,提升排查效率。
  2. 掌握top + jstack作为基础技能,应对无外网或受限环境。
  3. 定期监控系统指标,结合Prometheus + Grafana实现告警。
  4. 优化代码逻辑,避免死循环、高复杂度算法、过度创建线程。
  5. 合理配置JVM参数和线程池,避免资源浪费。

结语
CPU打满并不可怕,关键在于快速定位、精准修复。掌握本文介绍的两种排查方式,你将能在面对线上性能问题时从容不迫,成为团队中不可或缺的技术骨干。

到此这篇关于详解Java程序导致CPU打满如何排查的文章就介绍到这了,更多相关Java CPU打满内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实用技巧:如何使用String去除开头的第一个字符?

    Java实用技巧:如何使用String去除开头的第一个字符?

    这篇文章主要介绍了Java实用技巧:如何使用String去除开头的第一个字符,需要的朋友可以参考下
    2023-11-11
  • SpringBoot如何从配置文件中读取配置参数

    SpringBoot如何从配置文件中读取配置参数

    这篇文章主要介绍了SpringBoot如何从配置文件中读取配置参数问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 举例讲解Java的JSON类库GSON的基本用法

    举例讲解Java的JSON类库GSON的基本用法

    GSON是谷歌在GitHub上开源的Java的JSON格式转换类库,能够实现Java对象向JSON的序列和反序列,这里我们就来举例讲解Java的JSON类库GSON的基本用法:
    2016-06-06
  • java使用poi读取ppt文件和poi读取excel、word示例

    java使用poi读取ppt文件和poi读取excel、word示例

    这篇文章主要介绍了java使用poi读取ppt文件和poi读取excel、word示例,需要的朋友可以参考下
    2014-03-03
  • Java实现XML文件学生通讯录

    Java实现XML文件学生通讯录

    这篇文章主要为大家详细介绍了Java实现XML文件学生通讯录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • 详解java迭代器模式

    详解java迭代器模式

    这篇文章主要介绍了java迭代器模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • 全面了解java异常

    全面了解java异常

    本文非常详细的介绍了java异常,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们可以学习一下这篇文章
    2021-08-08
  • maven项目切换JDK踩坑指南分享

    maven项目切换JDK踩坑指南分享

    文章介绍了如何在Windows系统中配置多版本JDK环境,并解决环境变量配置失效的问题,同时,还提供了在IntelliJ IDEA中配置不同项目JDK版本的方法
    2024-11-11
  • 详解Java的Hibernate框架中的注解与缓存

    详解Java的Hibernate框架中的注解与缓存

    这篇文章主要介绍了详解Java的Hibernate框架中的注解与缓存,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下
    2015-12-12
  • SpringCloud alibaba使用Nacos配置中心解读

    SpringCloud alibaba使用Nacos配置中心解读

    本文介绍了如何在Spring Boot项目中使用Spring Cloud Alibaba和Nacos进行依赖管理、配置管理及配置刷新,通过添加BOM和配置文件,可以简化依赖版本的管理,并实现配置的动态刷新,同时,还展示了如何通过自定义DataId进行配置管理,并详细介绍了各种配置方式的优先级
    2026-01-01

最新评论