生产环境 cpu 飙高,如何排查

生产环境 cpu 飙高,如何排查

🎯 记住核心:ps -mp 找线程 → printf 转16进制 → jstack 看堆栈 → 定位代码!

一、标准排查流程(5步定位法)

第1步:确认是哪个进程占用CPU高(10秒)
# 方法1:查看所有进程CPU占用(按CPU降序)
ps aux --sort=-%cpu | head -10

# 方法2:实时监控
top
# 按 Shift + P(按CPU排序)

# 输出示例:
# USER   PID  %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
# app   12345 187.5 35.2 8234568 2345678 ?  Sl  10:23 125:34 java -jar myapp.jar

记录关键信息:

  • 进程PID:12345
  • CPU占用:187.5%(多核系统,单核100%,4核400%)
  • 进程命令:java -jar myapp.jar
第2步:找出该进程中CPU占用高的线程(30秒)

# 使用ps命令查看进程的所有线程,按CPU降序
ps -mp <PID> -o THREAD,tid,time,%cpu | sort -k4 -rn | head -10

# 实际命令示例
ps -mp 12345 -o THREAD,tid,time,%cpu | sort -k4 -rn | head -10

# 输出示例:
# USER     %CPU PRI SCNT WCHAN  USER SYSTEM   TID     TIME
# app      87.3  19    - -        -      -   23456  0:23:15  ← 热点线程
# app      45.2  19    - -        -      -   23457  0:18:42
# app       5.1  19    - -        -      -   23458  0:00:12

或使用top命令查看线程:


top -H -p 12345
# -H 显示线程
# -p 指定进程PID

记录高CPU的线程TID:

  • TID(线程ID):23456
  • CPU占用:87.3%
第3步:TID转换为16进制(5秒)

# jstack导出的堆栈中,线程nid是16进制格式,需要转换
printf "0x%x\n" <TID>

# 实际命令示例
printf "0x%x\n" 23456
# 输出:0x5ba0

记录16进制TID:0x5ba0

第4步:导出线程堆栈(1分钟)

# 导出jstack堆栈到文件
jstack <PID> > thread_dump_$(date +%Y%m%d_%H%M%S).txt

# 实际命令示例
jstack 12345 > thread_dump_20250105_143022.txt

# 建议连续导出3次(间隔3秒),对比分析
jstack 12345 > thread_dump_1.txt && sleep 3 && \
jstack 12345 > thread_dump_2.txt && sleep 3 && \
jstack 12345 > thread_dump_3.txt

第5步:搜索热点线程堆栈并定位代码(2分钟)

# 使用grep搜索对应的线程(用16进制nid)
grep -A 50 "nid=0x5ba0" thread_dump_1.txt

# 输出示例:
"http-nio-8080-exec-23" #45 daemon prio=5 os_prio=0 tid=0x00007f8b3c012800 nid=0x5ba0 runnable
   java.lang.Thread.State: RUNNABLE
        at com.example.service.OrderService.checkOrderStatus(OrderService.java:156)
        at com.example.service.OrderService.processOrder(OrderService.java:89)
        at com.example.controller.OrderController.createOrder(OrderController.java:45)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        ...

关键信息提取:

  • 线程名:http-nio-8080-exec-23
  • 线程状态:RUNNABLE(正在运行)
  • 问题代码:OrderService.java:156
  • 方法名:checkOrderStatus()

二、快速判断问题类型

堆栈特征问题类型
RUNNABLE + 同一方法反复出现死循环
Pattern$Loop.match正则回溯
GC.task_thread频繁Full GC
BLOCKED + waiting to lock锁竞争
Socket/MySQL相关IO阻塞/慢SQL

三、常见问题案例与修复

案例1:死循环空转(CPU 100%)

堆栈特征:

at com.example.service.MessageConsumer.consume(MessageConsumer.java:45)
at com.example.service.MessageConsumer.consume(MessageConsumer.java:45)  // 重复
at com.example.service.MessageConsumer.consume(MessageConsumer.java:45)  // 重复

问题代码:

// ❌ 错误代码
while (true) {
    Message msg = queue.poll();  // 非阻塞,立即返回
    if (msg == null) {
        continue;  // 空转!CPU 100%
    }
    processMessage(msg);
}

修复方案:

// ✅ 正确代码
while (!Thread.interrupted()) {
    Message msg = queue.poll(100, TimeUnit.MILLISECONDS);  // 阻塞等待
    if (msg != null) {
        processMessage(msg);
    }
}
案例2:正则表达式灾难性回溯

堆栈特征:

at java.util.regex.Pattern$Loop.match(Pattern.java:4785)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4717)
at com.example.util.EmailValidator.validate(EmailValidator.java:23)

问题代码:

// ❌ 危险正则(嵌套量词)
Pattern pattern = Pattern.compile("^([a-zA-Z0-9]+)*@([a-zA-Z0-9]+)*\\.com$");
pattern.matcher("aaaaaaaaaaaaaaaaaaaaX").matches();  // 指数级回溯

修复方案:

// ✅ 简化正则
Pattern pattern = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");

// ✅ 加长度限制
if (email.length() > 100) {
    return false;
}
案例3:频繁Full GC导致CPU高

判断方法:

# 查看GC统计
jstat -gc <PID> 1000 10

# 关键指标:
# FGC  - Full GC次数(快速增长)
# FGCT - Full GC总耗时(持续增加)
# OU   - Old区使用量
# OC   - Old区容量

# 示例输出:
# S0C   S1C    S0U  S1U    EC      EU       OC       OU      FGC  FGCT
# 10240 10240  0    8192   81920   45000   204800   198000  89   156.7
#                                                    ^^^^^^  ^^   ^^^^^
#                                                    Old区97% Full GC 89次

修复方案:

  • 增大堆内存:-Xms8g -Xmx8g
  • 排查内存泄漏:jmap -histo:live | head -20
  • 优化代码:减少大对象创建、使用对象池
案例4:synchronized锁竞争

堆栈特征:

"http-nio-8080-exec-25" waiting for monitor entry
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.cache.CacheService.get(CacheService.java:45)
        - waiting to lock <0x00000000e1234567> (a java.util.HashMap)
        - locked by "http-nio-8080-exec-12"  // 被其他线程持有

问题代码:

// ❌ 锁粒度太大
public synchronized User getUser(Long id) {
    return userCache.get(id);
}

修复方案:

// ✅ 使用并发集合
private ConcurrentHashMap<Long, User> userCache = new ConcurrentHashMap<>();

public User getUser(Long id) {
    return userCache.get(id);  // 无锁读
}

四、一键排查脚本(推荐使用)

#!/bin/bash
# 保存为 cpu_diagnosis.sh

echo "========== CPU飙高诊断 =========="

# 1. 找到CPU最高的Java进程
PID=$(ps aux --sort=-%cpu | grep java | grep -v grep | head -1 | awk '{print $2}')
echo "Java进程PID: $PID"

# 2. 显示CPU类型
echo -e "\n=== CPU类型分布 ==="
top -bn1 | grep "Cpu(s)" | awk '{print "User:" $2, "System:" $4, "IOWait:" $10}'

# 3. 显示Top 5 CPU线程
echo -e "\n=== Top 5 CPU线程 ==="
echo "TID        %CPU    HEX_TID"
ps -mp $PID -o THREAD,tid,time,%cpu | sort -k4 -rn | awk 'NR>1 && NR<=6 {
    tid=$2; cpu=$4;
    if (tid != "-") {
        cmd="printf \"0x%x\" " tid;
        cmd | getline hex;
        close(cmd);
        printf "%-10s %-7s %s\n", tid, cpu"%", hex;
    }
}'

# 4. 导出堆栈
echo -e "\n=== 导出堆栈 ==="
DUMP_FILE="thread_dump_$(date +%H%M%S).txt"
jstack $PID > $DUMP_FILE
echo "堆栈已导出: $DUMP_FILE"

# 5. 提示下一步
echo -e "\n=== 下一步 ==="
echo "使用以下命令查看热点线程堆栈:"
echo "grep -A 50 'nid=0x<HEX_TID>' $DUMP_FILE"

echo -e "\n========== 诊断完成 =========="

使用方法:


# 1. 保存脚本
vim cpu_diagnosis.sh
# 粘贴上述脚本内容

# 2. 添加执行权限
chmod +x cpu_diagnosis.sh

# 3. 执行(需要root或应用用户权限)
sudo ./cpu_diagnosis.sh

复杂版:

#!/bin/bash
# cpu_diagnosis.sh - CPU飙高自动诊断脚本

set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}   CPU飙高问题自动诊断工具 v1.0${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""

# 1. 找到CPU最高的Java进程
echo -e "${YELLOW}[Step 1] 定位问题进程...${NC}"
JAVA_PID=$(ps aux --sort=-%cpu | grep java | grep -v grep | head -1 | awk '{print $2}')

if [ -z "$JAVA_PID" ]; then
    echo -e "${RED}错误:未找到Java进程${NC}"
    exit 1
fi

JAVA_CMD=$(ps -p $JAVA_PID -o cmd --no-headers | cut -c1-80)
CPU_USAGE=$(ps -p $JAVA_PID -o %cpu --no-headers)

echo -e "  ${GREEN}${NC} 目标进程: PID=${JAVA_PID}"
echo -e "  ${GREEN}${NC} CPU使用率: ${CPU_USAGE}%"
echo -e "  ${GREEN}${NC} 命令: $JAVA_CMD"
echo ""

# 2. 显示CPU类型分布
echo -e "${YELLOW}[Step 2] 分析CPU类型分布...${NC}"
top -bn1 | grep "Cpu(s)" | \
awk -v red="$RED" -v green="$GREEN" -v yellow="$YELLOW" -v nc="$NC" '{
    us=$2; sy=$4; wa=$10; id=$8;
    printf "  用户态(us): " us " ";
    if (us+0 > 80) printf red "⚠️ 高" nc; else printf green "✓" nc;
    printf "\n";
    printf "  系统态(sy): " sy " ";
    if (sy+0 > 30) printf red "⚠️ 高" nc; else printf green "✓" nc;
    printf "\n";
    printf "  IO等待(wa): " wa " ";
    if (wa+0 > 20) printf red "⚠️ 高" nc; else printf green "✓" nc;
    printf "\n";
    printf "  空闲(id): " id "\n";
}'
echo ""

# 3. 找出Top 5 CPU线程
echo -e "${YELLOW}[Step 3] 定位热点线程...${NC}"
echo "  TID        CPU%    HEX_TID"
echo "  --------------------------------"
ps -mp $JAVA_PID -o THREAD,tid,time,%cpu --sort=-%cpu | awk 'NR>1 && NR<=6 {
    tid=$2; cpu=$4;
    if (tid != "-") {
        cmd="printf \"0x%x\" " tid;
        cmd | getline hex;
        close(cmd);
        printf "  %-10s %-7s %s\n", tid, cpu"%", hex;
    }
}'
echo ""

# 4. 快速检查GC情况
echo -e "${YELLOW}[Step 4] 检查GC状态...${NC}"
if command -v jstat &> /dev/null; then
    jstat -gc $JAVA_PID 1000 3 2>/dev/null | tail -2 | \
    awk 'NR==1{
        ou=$7; oc=$8; fgc=$14; fgct=$15;
        usage=ou/oc*100;
        printf "  Old区使用率: %.1f%%", usage;
        if (usage > 95) printf " " red "⚠️ 接近满" nc;
        printf "\n";
        printf "  Full GC次数: %s (总耗时: %ss)\n", fgc, fgct;
    }'
else
    echo -e "  ${YELLOW}⚠ jstat未安装,跳过GC检查${NC}"
fi
echo ""

# 5. 导出线程堆栈
echo -e "${YELLOW}[Step 5] 导出线程堆栈...${NC}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DUMP_DIR="cpu_issue_$TIMESTAMP"
mkdir -p $DUMP_DIR

if command -v jstack &> /dev/null; then
    for i in {1..3}; do
        jstack $JAVA_PID > "$DUMP_DIR/thread_dump_$i.txt" 2>/dev/null
        echo -e "  ${GREEN}${NC} thread_dump_$i.txt"
        sleep 2
    done
    
    # 统计线程状态
    echo ""
    echo "  线程状态分布:"
    jstack $JAVA_PID 2>/dev/null | grep "java.lang.Thread.State" | sort | uniq -c | \
    while read count state; do
        printf "    %-30s: %s\n" "$state" "$count"
    done
else
    echo -e "  ${RED}✗ jstack未安装${NC}"
fi
echo ""

# 6. 系统信息
echo -e "${YELLOW}[Step 6] 导出系统信息...${NC}"
top -H -p $JAVA_PID -b -n 1 > "$DUMP_DIR/top.txt" 2>/dev/null && \
    echo -e "  ${GREEN}${NC} top.txt"
ps -mp $JAVA_PID -o THREAD,tid,time,%cpu --sort=-%cpu > "$DUMP_DIR/ps_threads.txt" 2>/dev/null && \
    echo -e "  ${GREEN}${NC} ps_threads.txt"
vmstat 1 5 > "$DUMP_DIR/vmstat.txt" 2>/dev/null && \
    echo -e "  ${GREEN}${NC} vmstat.txt"
echo ""

# 7. 打包
echo -e "${YELLOW}[Step 7] 打包诊断文件...${NC}"
tar czf "${DUMP_DIR}.tar.gz" $DUMP_DIR 2>/dev/null
echo -e "  ${GREEN}${NC} ${DUMP_DIR}.tar.gz"
echo ""

# 8. 生成分析建议
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}   诊断完成!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e "${YELLOW}📋 下一步操作:${NC}"
echo ""
echo "1. 查看热点线程堆栈:"
echo "   grep -A 50 'nid=0x<HEX_TID>' $DUMP_DIR/thread_dump_1.txt"
echo ""
echo "2. 搜索常见问题特征:"
echo "   grep -r 'Pattern\$Loop' $DUMP_DIR/"
echo "   grep -r 'BLOCKED' $DUMP_DIR/"
echo ""
echo "3. 如果怀疑GC问题:"
echo "   jmap -heap $JAVA_PID"
echo "   jmap -histo:live $JAVA_PID | head -20"
echo ""
echo "4. 生成火焰图(推荐):"
echo "   java -jar arthas-boot.jar"
echo "   profiler start && sleep 30 && profiler stop --format html"
echo ""
echo -e "${YELLOW}📦 诊断文件已保存到: ${DUMP_DIR}.tar.gz${NC}"

五、高级工具:Arthas(强烈推荐)

Arthas是阿里开源的Java诊断神器,无需修改代码、无需重启应用。

安装使用:


# 1. 下载
wget https://arthas.aliyun.com/arthas-boot.jar

# 2. 启动(自动列出Java进程,选择目标进程)
java -jar arthas-boot.jar

# 3. 核心命令
# 查看CPU最高的3个线程(自动显示堆栈)
thread -n 3

# 查看指定线程
thread <tid>

# 查找死锁
thread -b

# 实时监控面板
dashboard

# 生成火焰图(最直观)
profiler start
# 等待30-60秒
profiler stop --format html
# 生成 arthas-output/profiler-timestamp.html

Arthas优势:
✅ 自动找到高CPU线程
✅ 自动显示完整堆栈
✅ 生成火焰图(可视化)
✅ 无需TID转16进制
✅ 实时监控

六、预防措施

  1. 代码层面:
✅ 循环优化
  □ 避免while(true)空转,使用阻塞等待
  □ 避免在循环内创建对象(如Pattern.compile)
  □ 使用懒加载,按需计算
  
✅ 正则表达式
  □ 避免嵌套量词:(a+)+、(a*)*
  □ 输入长度限制(如<100字符)
  □ 复杂正则添加超时保护
  
✅ 集合操作
  □ 并发场景使用ConcurrentHashMap
  □ 指定集合初始容量,避免扩容
  □ 大集合分批处理
  
✅ 数据库
  □ 避免N+1查询,使用JOIN
  □ 使用分页查询(LIMIT)
  □ 添加合适索引
  □ 使用连接池
  
✅ 并发控制
  □ 锁粒度最小化
  □ 使用ConcurrentHashMap等并发集合
  □ 避免在synchronized内调用外部方法
  □ 线程池配置合理:核心数=CPU核数*2
  
✅ 资源管理
  □ 及时关闭资源(try-with-resources)
  □ 使用对象池(数据库连接、HTTP连接)
  □ 避免内存泄漏(静态集合、ThreadLocal)

  1. JVM参数:

# 生产环境推荐配置
java -server \
     -Xms8g -Xmx8g \                              # 堆内存初始=最大
     -Xmn2g \                                     # 年轻代2GB
     -XX:MetaspaceSize=256m \                     # 元空间
     -XX:MaxMetaspaceSize=512m \                  # 元空间最大值
     -XX:+UseG1GC \                               # G1垃圾收集器
     -XX:MaxGCPauseMillis=200 \                   # GC暂停目标200ms
     -XX:G1HeapRegionSize=16m \                   # G1 Region大小
     -XX:+HeapDumpOnOutOfMemoryError \            # OOM时自动Dump
     -XX:HeapDumpPath=/data/logs/heap_dump.hprof \
     -XX:+PrintGCDetails \                        # 打印GC详情
     -XX:+PrintGCDateStamps \                     # 打印GC时间戳
     -Xloggc:/data/logs/gc_%p_%t.log \            # GC日志路径
     -XX:+UseGCLogFileRotation \                  # GC日志滚动
     -XX:NumberOfGCLogFiles=10 \                  # 保留10个GC日志
     -XX:GCLogFileSize=100M \                     # 每个日志100MB
     -XX:+UnlockDiagnosticVMOptions \             # 解锁诊断选项
     -XX:+PrintSafepointStatistics \              # 打印安全点统计
     -XX:PrintSafepointStatisticsCount=1 \        # 每次都打印
     -jar app.jar

八、快速参考卡

CPU飙高排查5步速查表

  • 1️⃣ ps aux --sort=-%cpu → 找进程PID
  • 2️⃣ ps -mp -o THREAD → 找线程TID
  • 3️⃣ printf “0x%x” → TID转16进制
  • 4️⃣ jstack → 导出堆栈
  • 5️⃣ grep “nid=0x” → 定位代码

快捷命令:

  • ps -mp -o THREAD,tid,%cpu | sort -k4 -rn
  • jstack | grep -A 50 “nid=0x”

神器:Arthas

  • java -jar arthas-boot.jar
  • thread -n 3
  • profiler start && profiler stop

经验总结:

  • 90%的CPU飙高都是代码问题(死循环、正则、GC)
  • 堆栈连续导出3次对比分析更准确
  • 保留现场比快速重启更重要
  • 预防胜于治疗,代码审查+监控告警

最后忠告:线上问题,先保留现场,再重启应用!

### 生产环境CPU占用排查工具及性能分析 在生产环境中,当遇到CPU占用的问题时,可以使用多种工具进行排查和性能分析。以下是一些常用的工具及其功能: 1. **top** `top` 是一个实时显示系统中各个进程资源占用情况的工具,可以快速查看哪些进程占用了较CPU资源。通过该工具,可以初步定位到CPU占用的进程PID。[^2] ```bash top ``` 2. **ps** `ps` 命令可以结合其他参数查看具体线程的CPU占用情况,并按CPU占用排序。例如,可以通过以下命令找到CPU占用的线程ID。 ```bash ps -mp <PID> -o THREAD,tid,time | sort -rn ``` 3. **jstack** `jstack` 是JDK自带的工具,用于生成Java进程的线程转储信息。通过线程转储,可以分析出哪个线程导致了CPU占用。将线程ID转换为十六进制后,可以在线程转储中查找对应的信息。 ```bash jstack <PID> | grep <TID_in_hex> -A 30 ``` 4. **jconsole** 和 **jvisualvm** 这两个工具是JDK自带的图形化监控工具,可以实时监控Java应用的性能指标,包括CPU、内存、线程等。它们适合用于开发环境或测试环境中的详细性能分析。[^1] 5. **Arthas** Arthas 是阿里巴巴开源的一款Java诊断工具,支持在线诊断运行中的Java应用。通过`dashboard`命令可以查看当前系统的资源使用情况,而`thread`命令可以查看线程的CPU占用情况并定位具体的业务代码。[^4] ```bash java -jar arthas-boot.jar dashboard thread -n 3 ``` 6. **strace** `strace` 是一个用于跟踪进程系统调用和信号的工具。虽然它主要用于非Java程序的调试,但在某些情况下也可以帮助分析Java程序的行为。[^2] ```bash strace -p <PID> ``` 7. **perf** `perf` 是Linux内核自带的一个性能分析工具,可以用来分析CPU热点函数。通过与`perf report`结合使用,可以找到消耗CPU最多的函数。 ```bash perf record -g -p <PID> perf report ``` 8. **sysdig** `sysdig` 是一个强大的系统级监控和故障排除工具,可以捕获和分析系统调用。它提供了更丰富的功能,适用于复杂的性能分析场景。 ```bash sysdig -p "%proc.name %cpu" evt.type=cpu ``` 9. **htop** `htop` 是`top`的增强版,提供了更友好的界面和更多的功能,如交互式进程管理、树状视图等。[^3] ```bash htop ``` ### 示例代码:模拟CPUJava程序 以下是一个简单的Java程序,用于模拟CPU占用的情况。可以通过上述工具对其进行分析。 ```java public class CpuHighDemo { public static void main(String[] args) { while (true) { // 空循环导致CPU占用 } } } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值