CPU 100% 了怎么办?Java 性能排障的标准化操作

前言

线上告警:CPU 飙到 100%,服务响应超时。开终端、连服务器、top、jstack......

这一套流程对老手来说行云流水,但对新同学来说,每次遇到性能问题都可能是一次手忙脚乱------"先看什么?用什么命令?怎么看线程栈?"

这篇文章总结一套标准化的 Java 性能排障流程,遇到 CPU 飙升、内存泄漏、响应延迟问题时,按步骤来就行。

一、CPU 飙升排查(黄金流程)

第 1 步:找到高 CPU 进程

bash 复制代码
# Linux
top

# 找到进程 PID,比如 Java 进程 pid=12345

输出示例:

perl 复制代码
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM
12345 app       20   0 4.5g 1.2g  15m S  98.0 15.0  java

如果机器上只有一个 Java 进程,基本就是它了。

第 2 步:找到高 CPU 的线程

bash 复制代码
top -H -p 12345

输出中会显示该进程下的所有线程及其 CPU 占用。找到 CPU 占用最高的线程,记下它的线程 ID(TID),比如 12399

第 3 步:线程 ID 转 16 进制

bash 复制代码
printf "%x\n" 12399
# 输出: 306f

这个 16 进制值 0x306f 是后续在 jstack 中定位线程的依据。

第 4 步:导出线程栈并定位

bash 复制代码
jstack 12345 | grep -A 30 "0x306f"

输出会显示该线程的执行栈:

ruby 复制代码
"http-nio-8080-exec-3" #45 daemon prio=5 os_prio=0 tid=0x00007f...
   java.lang.Thread.State: RUNNABLE
    at com.example.service.PremiumCalculator.calculate(PremiumCalculator.java:125)
    at com.example.service.PremiumCalculator.lambda$batchCalculate$0(PremiumCalculator.java:78)
    ...

重点关注:RUNNABLE 状态 + 热点方法的行号------这就是 CPU 的"元凶"。

常见 CPU 飙升根因

根因类型 特征
死循环 (while true) 线程栈看上去在一个循环方法里反复执行
频繁 GC (GC 线程抢占 CPU) jstack 中有大量 GC 线程,配合 jstat -gcutil 确认
大量正则匹配 栈中能看到 Pattern.matcher 或 match()
序列化/反序列化热点 栈中能看到 JSON/XML 解析相关调用
大对象频繁创建 栈中能看到循环创建新对象(StringBuilder、集合等)

二、内存泄漏排查

第 1 步:确认内存泄漏

bash 复制代码
# 查看 JVM 堆内存变化
jstat -gcutil <pid> 2000 10
复制代码
 S0    S1    E    O    M   YGC   YGCT  FGC  FGCT
 0.00 96.32 88.5 92.3 95.2   452  3.245   38  12.56
 0.00 95.89 90.2 93.1 95.1   453  3.248   39  13.02

关注 O(老年代)和 FGC(Full GC 次数)。老年代持续增长 + Full GC 越来越频繁 → 大概率内存泄漏。

第 2 步:堆转储(Heap Dump)

bash 复制代码
# 方式一:运行时生成(服务不停)
jmap -dump:live,format=b,file=/tmp/heap.hprof <pid>

# 方式二:JVM 启动参数,OOM 时自动生成(推荐)
# -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/

第 3 步:分析 Dump 文件

用 MAT(Eclipse Memory Analyzer)或 VisualVM 打开 heap.hprof:

  • 查看 Leak Suspects:工具自动推测泄漏点
  • 查看 Dominator Tree:按保留堆大小排序,找出最大的对象
  • 查看 GC Roots:确认对象被谁引用,为什么没有被回收

常见的泄漏模式

  1. ThreadLocal 未清理:线程池中的 ThreadLocal 没有在 finally 中 remove
  2. HashMap key 未实现 equals/hashCode:反复 put 相同逻辑 key 的不同对象,永远不会覆盖
  3. 静态集合作为缓存:static Map 只增不减
  4. ClassLoader 泄漏:热加载场景下的类卸载问题

三、响应延迟(RT 飙高)排查

第 1 步:区分是 CPU 问题还是 IO 等待

bash 复制代码
# 查看 CPU 等待 IO 的比例
iostat -x 1
  • %iowait 高 → IO 瓶颈
  • %user 高 → CPU 绑定型问题
  • 都不高 → 外部依赖(DB、Redis、外部 API)慢

第 2 步:外部依赖排查

bash 复制代码
# 慢 SQL 排查
# 打开 MySQL 慢查询日志,或直接看 DBA 平台

# 查看 Redis 慢查询
SLOWLOG GET 10

# 查看外部 API 调用耗时:用 APM 工具(SkyWalking / Pinpoint)或业务日志中的耗时埋点

第 3 步:锁竞争排查

如果 jstack 中看到大量 BLOCKEDWAITING 状态的线程,可能是锁竞争:

bash 复制代码
# 统计线程状态
jstack <pid> | grep "java.lang.Thread.State:" | sort | uniq -c

输出示例:

yaml 复制代码
  45 java.lang.Thread.State: RUNNABLE
  15 java.lang.Thread.State: BLOCKED     ← 锁竞争严重
  10 java.lang.Thread.State: TIMED_WAITING

BLOCKED 多的线程,用 jstack 追它们的锁对象,看哪个锁被谁持有。

四:排障工具箱速查

场景 命令/工具 作用
CPU 高 top -H -p + jstack 定位热点线程和方法
内存泄漏 jstat -gcutil + jmap heap dump 确认泄漏 + 分析根因
GC 问题 jstat -gcutil 查看 GC 频率和耗时
IO 瓶颈 iostat -x 查看 IO 等待比例
网络延迟 ping / curl -w 确认网络连通性和延迟
慢 SQL MySQL SLOWLOG / DBA 平台 定位慢查询
锁竞争 jstack 查看线程状态分布
全链路追踪 SkyWalking / Arthas APM 监控 + 在线诊断

总结

性能排障的核心不是背命令,而是形成标准化的排查思路

  1. CPU 高 → top → jstack → 热点方法
  2. 内存泄漏 → jstat → jmap heap dump → MAT 分析
  3. 响应慢 → 区分 CPU/IO/锁 → 逐一排查

遇到问题不慌,按这个流程走,99% 的性能问题都能在 15 分钟内定位到根因。

你常用的排障工具有哪些?欢迎补充你的压箱底技巧。

相关推荐
鱼人1 小时前
Redis、网关负载均衡为什么不能用普通取模哈希?
后端
juejin9982 小时前
Claude Code Lab-3(下):三能力 MCP Server
后端
java小白小2 小时前
SpringBoot(07):事务管理——@Transactional 你真的用对了吗?
后端
shepherd1112 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
java小白小2 小时前
SpringBoot(05):Spring Data JPA——用面向对象的方式操作数据库
后端
juejin9983 小时前
Claude Code Lab-2(上):自然语言查库助手
后端
java小白小3 小时前
SpringBoot(06):多数据源配置——一个项目连多个库怎么做
后端
程序员cxuan4 小时前
Codex 会把磁盘给烧了?完整复盘来了!
人工智能·后端·程序员
ClouGence4 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle