第一步:宏观定位 - 找到问题进程和线程

        1. 找到最耗 CPU 的 Java 进程

# 按 CPU 使用率排序,显示进程 ID 和命令
top -c
# 或者使用更直观的 htop(如果已安装)
htop

观察要点:

  • 找到 CPU 使用率最高的 Java 进程,记录其 PID

        2. 找到该进程中最耗 CPU 的线程

# 查看指定进程中各个线程的 CPU 使用情况
top -H -p <java_pid>
# 或者
ps -eL -o pid,tid,pcpu,pmem,args | grep <java_pid> | sort -nr -k3 | head -10

观察要点:

  • 记录消耗 CPU 最高的几个线程的 TID(十进制)

  • 将这些 TID 转换为十六进制(为后续 jstack 分析做准备)

第二步:深入分析 - 查看线程在做什么

        3. 使用 jstack 分析线程堆栈

# 获取 Java 进程的线程堆栈
jstack <java_pid> > jstack.log

# 或者在容器环境中(如果 jstack 不可用)
kubectl exec -it <pod_name> -- jstack <java_pid> > jstack.log

        4. 关联高 CPU 线程与代码

        在 jstack.log 中搜索之前转换的十六进制 TID:

# 在堆栈文件中搜索特定线程
cat jstack.log | grep -A 20 <hex_tid>

常见的高 CPU 线程模式:

  • 死循环:线程一直处于 RUNNABLE 状态,重复执行相同方法

  • 密集计算:数学运算、加密解密、序列化等

  • GC 线程繁忙:频繁的垃圾回收

  • 锁竞争:线程在 BLOCKED 状态等待锁

第三步:专业工具 - 使用性能分析工具

        5. 使用 jstat 检查 GC 情况

# 每 1 秒采样一次 GC 情况,共 10 次
jstat -gcutil <java_pid> 1s 10

观察要点:

  • 如果 FGC/FGCT(Full GC 次数/时间)很高,可能是内存问题导致的 GC 频繁

  • 如果 E(Eden区)一直接近 100%,可能是对象创建太快

      6. 使用 jmap + MAT 分析内存(如果怀疑内存问题)

# 生成堆转储文件(生产环境慎用,会造成应用暂停)
jmap -dump:live,format=b,file=heap.hprof <java_pid>

# 或者只统计对象数量
jmap -histo:live <java_pid> | head -20

       7. 使用 Arthas(推荐)- 实时诊断神器

# 启动 Arthas
java -jar arthas-boot.jar

# 选择目标 Java 进程
# dashboard          # 查看实时系统面板
# thread -n 3        # 查看最忙的 3 个线程
# thread <tid>       # 查看指定线程堆栈
# profiler start     # 开始 CPU 性能分析
# profiler stop      # 停止并生成火焰图

        Arthas 关键命令

# 持续监控最忙的线程
thread -n 3 -i 1000

# 监控方法执行时间
trace com.example.xxxService expensiveMethod

# 生成火焰图(最直观定位热点方法)
profiler start
# 等待 30 秒...
profiler stop --format html

第四步:代码级定位 - 分析具体问题

        8. 分析火焰图

生成的火焰图可以直观显示 CPU 时间消耗在哪些方法上:

  • 宽平的栈顶:表示热点方法

  • 从下往上:调用关系

  • 点击具体栈帧:查看具体方法

9. 常见问题模式及代码示例

        案例 1:死循环

// 问题代码
public void run() {
    while (true) {  // 缺少退出条件
        // 处理业务
        processData();
    }
}

// 或者更隐蔽的
public void processQueue() {
    while (!queue.isEmpty()) {  // 如果queue被其他线程消费,可能永远不空
        Data data = queue.poll();
        handleData(data);
    }
}
        案例 2:算法复杂度高
// O(n^3) 的算法,数据量大时 CPU 爆炸
public void findTriplets(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            for (int k = j + 1; k < nums.length; k++) {
                if (nums[i] + nums[j] + nums[k] == 0) {
                    // 处理结果
                }
            }
        }
    }
}
        案例 4:频繁的序列化/反序列化
// 在循环内频繁创建 JSON 序列化器
public void processList(List<User> users) {
    for (User user : users) {
        // 每次循环都创建 ObjectMapper(昂贵操作)
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(user);  // CPU 密集型
        sendToQueue(json);
    }
}

// 优化:移到循环外
ObjectMapper mapper = new ObjectMapper();  // 单例
for (User user : users) {
    String json = mapper.writeValueAsString(user);
    sendToQueue(json);
}

第五步:系统级排查

        10. 检查应用日志

# 查看错误日志
tail -f /path/to/app/log/error.log

# 搜索特定异常
grep -r "OutOfMemoryError" /path/to/logs/
grep -r "StackOverflowError" /path/to/logs/
grep -r "deadlock" /path/to/logs/

总结排查思路

  1. 宏观定位top → 找到问题 PID

  2. 线程分析top -H → 找到问题 TID → 转换十六进制

  3. 堆栈分析jstack → 关联 TID 与代码

  4. 工具深入Arthas/jstat/火焰图 → 定位热点方法

  5. 代码修复:根据分析结果修改问题代码

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐