线上CPU100%问题总结及解决方案

一、引言

我们在生产实践中或多或少遇见过CPU100%的问题,如果没有处理过这些问题,第一次遇见多少有点手忙脚乱不知道该怎么处理,而且这个问题也是面试过程中老生常谈的问题,掌握和解决这类问题对我们提升有很大的帮助,接下来我们就来聊一下有哪些问题会导致CPU100%,以及该如何处理解决这类问题。

二、常见业务场景

2.1 死循环导致 CPU 占用

死循环是最常见的导致 CPU 使用率 100% 的原因之一。程序没有正确终止循环或出现逻辑错误,导致程序在无限执行某些操作,消耗大量的 CPU 资源。

public class InfiniteLoopExample {
    public static void main(String[] args) {
        // 错误的循环条件,导致死循环
        while (true) {
            // 执行一些繁重的计算,消耗 CPU
            int result = 0;
            for (int i = 0; i < 1000000; i++) {
                result += i;
            }
        }
    }
}

优化建议:确保循环有合适的退出条件。如果需要某些长时间运行的任务,考虑将其分解为多个可管理的小任务,避免过度占用 CPU。

2.2 线程创建过多导致资源消耗

如果程序在高并发情况下没有有效的线程池管理,直接创建大量线程,每个线程都进行计算或 I/O 操作,会导致 CPU 资源被过度占用,最终可能引发 100% CPU 占用。

public class HighConcurrencyExample {
    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                while (true) {
                    // 执行高消耗的任务
                    Math.pow(Math.random(), Math.random());
                }
            }).start();
        }
    }
}

 优化建议:使用线程池(如 ExecutorService)管理线程。避免每次都创建新的线程,而是复用已存在的线程,合理控制并发数。

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100000; i++) {
    executor.submit(() -> {
        // 执行任务
    });
}

2.3 频繁的垃圾回收 (GC) 导致 CPU 占用

如果应用程序在运行时频繁创建和销毁大量对象,JVM 的垃圾回收(GC)机制可能频繁触发。GC 过程会占用大量 CPU 资源,尤其是在大堆内存环境下。例如,大量临时对象的创建导致 JVM 不断触发 Full GC 或 Minor GC,导致 CPU 占用率飙升。

public class GCDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            // 创建大量临时对象,触发 GC
            new String("Temporary Object");
        }
    }
}

优化建议:

  • 减少短生命周期对象的创建,尽量重用对象,减少 GC 压力。
  • 可以通过优化对象池、调整 JVM 的 GC 参数,合理管理内存使用,减少 GC 的频率。
  • 监控 JVM 内存和 GC 行为,使用工具如 JVisualVM 进行性能分析。

2.4 高并发情况下的线程竞争(Lock)导致的 CPU 100%

在高并发环境下,如果多个线程频繁竞争同一资源,导致大量线程被阻塞并且无法释放 CPU,这样会导致 CPU 使用率居高不下,甚至达到 100%。例如,多个线程在同时访问同一个共享资源时,如果加锁不当(例如锁粒度过粗),可能导致多个线程无法并发执行,CPU 被锁操作占用。

public class ThreadLockExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    // 执行一些计算,导致线程被阻塞
                    for (int j = 0; j < 1000000; j++) {
                        Math.pow(Math.random(), Math.random());
                    }
                }
            }).start();
        }
    }
}

 优化建议:

  • 尽量避免粗粒度锁,考虑使用更细粒度的锁(如读写锁 ReadWriteLock)。
  • 使用 ThreadPoolExecutor 等线程池进行任务调度,避免过多线程的创建。
  • 使用无锁算法(如 CAS)等优化竞争。

2.5 I/O 阻塞导致 CPU 占用

某些场景下,I/O 操作(如读取大文件、访问数据库等)可能会导致线程处于阻塞状态。如果应用使用了不当的 I/O 模型或阻塞 I/O 操作,会导致大量线程等待,从而加重 CPU 负担。如果应用程序需要频繁地进行 I/O 操作,但采用了阻塞式的 I/O 操作方式,可能会导致线程阻塞,从而影响 CPU 占用。

public class IOBlockingExample {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader("large_file.txt"));
        String line;
        while ((line = reader.readLine()) != null) {
            // 模拟处理每一行数据,可能引发 I/O 阻塞
            System.out.println(line);
        }
        reader.close();
    }
}

 优化建议:

  • 使用 非阻塞 I/O(NIO)或 异步 I/O,避免 I/O 阻塞。
  • 对于高并发 I/O 操作,考虑使用线程池或异步框架(如 Netty)处理请求。

三、使用jstack解决CPU100%

通过商量的了解我们都知道了CPU发生的场景都有那些,最常见的导致cou100%的就是死循环和死锁导致的,接下来我们就用一个死循环的场景来解决CPU100%,其他场景解决该问题的方式基本都是一样的。

@RequestMapping("loop")
    public String loop() {
        System.out.println("start");
        while (true) {}
    }

排查思路

(1)定位高负载的进程的PID:使用top命令或者top -c命令,查看当前 CPU消耗过高的进程PID。观察各个进程对资源的占有情况,我们可以直观的看到PID为1798的Java进程占用CPU最高,而且持续时间很长。

(2)根据 PID查出消耗 cpu最高的线程号: top -Hp 1798,按下P,进程按照 CPU使用率排序。找出最耗 CPU的线程,结果发现1798是就耗了99.9%。一般超过80%就是比较高的,80%左右是合理情况。这样我们就能得到CPU消耗比较高的线程id。

 (3)将十进制的进程PID转为十六进制:通过上面的操作我们知道了哪个线程占用的资源的占用的最高,将其转换为十六进制。执行printf  '%x\n'  PID命令,将十进制的1832转换为十六制为0x728。

(4)根据线程号查出对应的 java线程:通过top -Hp命令我们可以知道了占用资源最高的线程PID,如果该线程的COMMAND显示不是java,则在使用jstack命令报错无法使用(因为jstack命令只针对java线程),此时我们找到执行top命令时查找到的占用资源最高的java进程号PID,执行jstack 进程号PID  >  threadDump.txt ,将指定 PID 的线程转储输出到 threadDump.txt 文件中。

jstack  1798 > threadDump.txt

 (5)分析线程转储:打开 threadDump.txt 文件,全局查找第三步下的十六进制PID,如下图所示,我们可以清楚的看到导致CUP长时间100%的代码所在位置。

 四、使用arthas解决CPU占用很高的问题

使用arthas解决CPU占用很高的问题,相比较jstack要简单快捷的很多。主要用到两个命令:

  • dashboard 命令查看TOP N线程

  • thread 命令查看堆栈信息

(1) 下载arthas-boot.jar,然后用java -jar的方式启动,此时它会列举出我们正在运行的java程序,输入出现问题的应用程序序号,我们这里只有一个输入1,回车。

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

 (2)输入dashboard命令可以看到是哪个线程占用cpu最高

(3)接下来输入thread -n 3,表示最忙的前3个线程并打印信息。我们可以看到使用arthas和使用jstack得到的结果是一样的。

五、小结 

本文我们主要例举了导致CPU占用很高的业务场景,同时给出了当我们线上CPU占用很高的时,解决问题的两种方式jstack和arthas,在这里我们还是推荐使用arthas去解决问题,更加的简单,简捷。

<think>嗯,用户问的是Java的jar包运行一段时间后CPU占用率达到200%可能是什么问题。首先,我需要确认用户的使用场景。通常,CPU占用率高可能出现在服务器应用或者长时间运行的后台服务中。用户提到的是jar包,所以很可能是基于Spring Boot或者其他框架打包的应用。 接下来,我得考虑可能的原因。CPU使用率高通常与线程频繁执行计算密集型操作、死循环、锁竞争、频繁GC或者代码逻辑问题有关。用户提到的是运行一段时间后出现,所以可能不是启动时的问题,而是随着时间累积出现的,比如内存泄漏导致GC频繁,或者某个定时任务出了问题。 用户可能是在生产环境遇到这个问题,需要快速定位解决。他们可能没有详细的监控工具,所以需要一些基本的排查步骤,比如使用top命令查看进程,jstack获取线程转储,或者jstat查看GC情况。 另外,用户可能对Java内部机制不是很熟悉,所以需要用简单易懂的语言解释可能的原因,并提供具体的解决步骤。比如,如何检查线程状态,如何分析GC日志,或者如何优化代码。 还需要注意,CPU使用率200%意味着在多核系统上,两个核心被完全占用,这通常是因为有多个线程在并行执行高负载任务。可能的原因包括线程池配置不当,导致创建过多线程,或者某个循环没有正确的退出条件。 用户可能没有提到日志信息,所以需要建议他们检查应用日志,看是否有异常或错误信息。同时,也要考虑外部依赖,比如数据库查询变慢,导致线程阻塞,进而引起CPU飙升。 最后,需要给出一个结构化的回答,分点列出可能的原因解决方法,帮助用户逐步排查问题。可能还需要推荐一些工具,比如Arthas,来帮助诊断问题。</think>--- ### **1. 问题本质** Java进程的CPU占用率达到**200%**(在多核系统中,表示两个CPU核心被完全占用),通常由以下原因导致: - **线程密集计算**:代码中存在死循环、未优化的算法或高并发计算任务。 - **资源争用**:线程锁竞争(如`synchronized`、`ReentrantLock`)导致线程频繁阻塞唤醒。 - **垃圾回收(GC)频繁**:内存泄漏或堆配置不当引发频繁Full GC,消耗CPU资源。 - **外部依赖阻塞**:如慢SQL查询、网络IO阻塞导致线程池资源耗尽。 - **框架或库的Bug**:第三方库(如Spring、Netty)的异常行为导致CPU空转。 --- ### **2. 排查步骤** #### **步骤1:定位CPU线程** 1. **查看进程CPU占用**: ```bash top -H -p <java_pid> # 查看线程级别的CPU占用(Linux) ``` - 记录占用最高的线程ID(十进制转十六进制,用于后续分析)。 2. **生成线程快照**: ```bash jstack <java_pid> > thread_dump.txt # 输出线程堆栈 ``` - 在`thread_dump.txt`中搜索对应线程的十六进制ID(如`nid=0x3e7`),分析其堆栈信息。 #### **步骤2:常见问题模式分析** - **死循环/密集计算**: - 线程堆栈中显示长时间停留在同一方法(如`while(true)`、复杂数学运算)。 - **锁竞争**: - 多个线程状态为`BLOCKED`,且等待同一锁(如`synchronized`代码块)。 - **GC频繁**: - 使用`jstat`监控GC情况: ```bash jstat -gcutil <java_pid> 1000 # 每秒输出GC统计 ``` - 若`FGC`(Full GC次数)持续增长且`FGCT`(Full GC时间)显著,可能存在内存泄漏。 #### **步骤3:代码与依赖检查** - **检查高并发代码**: - 线程池配置是否合理(如核心线程数过大)。 - 是否存在未优化的循环或递归调用。 - **外部依赖分析**: - 数据库慢查询、Redis大Key、HTTP接口超时等可能导致线程阻塞。 - 使用`jstack`观察线程状态是否为`RUNNABLE`(CPU占用)或`WAITING`(IO阻塞)。 --- ### **3. 常见原因与解决方案** #### **(1) 代码死循环或密集计算** - **示例场景**: ```java // 错误示例:未设置退出条件的循环 while (true) { // 密集计算(如未批处理的流处理) } ``` - **解决**: - 优化算法复杂度(如减少嵌套循环)。 - 添加合理的退出条件或休眠(如`Thread.sleep()`)。 #### **(2) 线程锁竞争** - **示例场景**: ```java // 错误示例:粗粒度锁导致并发性能差 public synchronized void process() { // 耗时操作 } ``` - **解决**: - 缩小锁范围(如使用细粒度锁或`ConcurrentHashMap`)。 - 替换为无锁数据结构(如`AtomicInteger`)或读写锁(`ReentrantReadWriteLock`)。 #### **(3) 频繁垃圾回收(GC)** - **现象**: - `jstat`显示`FGC`频繁增长,`OU`(老年代使用量)接近100%。 - 日志中频繁出现`Full GC`。 - **解决**: - 分析堆转储(`jmap -dump:format=b,file=heap.hprof <pid>`),用MAT工具查找内存泄漏。 - 调整堆大小(如`-Xmx4g -Xms4g`)或选择更适合的GC算法(如G1)。 #### **(4) 框架或库的异常行为** - **典型问题**: - Spring Boot Actuator端点被频繁调用。 - Netty的EventLoop线程因任务堆积导致CPU满载。 - **解决**: - 检查框架配置(如Actuator的安全限制)。 - 使用`AsyncProfiler`或`Arthas`分析热点方法。 #### **(5) 外部依赖阻塞** - **示例场景**: - 数据库查询未加索引,导致单次查询耗时过长。 - HTTP客户端未设置超时,线程池被占满。 - **解决**: - 优化SQL(添加索引、分页查询)。 - 为外部调用设置超时(如`Feign.client.config.default.readTimeout=5000`)。 --- ### **4. 工具推荐** 1. **Arthas**: - 动态跟踪Java方法调用,定位CPU热点: ```bash trace com.example.Service * '#cost > 100' # 跟踪耗时超过100ms的方法 ``` 2. **AsyncProfiler**: - 生成火焰图,直观展示CPU占用分布: ```bash ./profiler.sh -d 30 -f flamegraph.html <java_pid> ``` 3. **VisualVM**: - 监控线程状态、内存GC活动。 --- ### **5. 示例场景** **问题**:某电商系统促销期间CPU飙升至200%,接口响应变慢。 **分析**: 1. `top`发现两个线CPU占用90%以上。 2. `jstack`显示线程处于`RUNNABLE`状态,堆栈指向`JSON.parseObject()`方法。 3. 代码中存在循环解析大JSON文件且未启用流式解析。 **解决**: - 改用`JsonReader`流式解析。 - 增加JVM堆大小:`-Xmx8g -Xms8g`。 - 优化JSON数据结构,减少嵌套层级。 --- ### **6. 预防措施** 1. **代码审查**:避免死循环、未优化的算法粗粒度锁。 2. **性能测试**:压测阶段使用JMeter模拟高并发,观察CPUGC。 3. **监控告警**:集成Prometheus + Grafana监控JVM指标。 4. **合理配置**:根据负载调整线程池大小JVM参数。 --- ### **总结** Java进程CPU占用200%的核心原因是 **线程长期占用CPU资源未释放**,可能由代码逻辑缺陷、锁竞争或GC频繁引发。通过`jstack`定位CPU线程、结合`jstat`堆转储分析,可快速定位问题根源。优化代码逻辑、调整JVM参数合理使用工具是解决问题的关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值