JVM CPU 飙高面试题完全指南 目录
一、问题背景 1.1 什么是 CPU 飙高 CPU 飙高 是指应用程序的 CPU 使用率异常升高,远超正常基线水平,导致系统响应变慢甚至服务不可用。
核心特征 :
✅ User CPU 高:业务逻辑计算密集
✅ Sys CPU 高:系统调用频繁(上下文切换、锁竞争)
✅ Wait CPU 高:I/O 阻塞等待
❌ CPU 使用率持续 > 80%
1.2 CPU 飙高的影响 🔥 直接影响 :
接口响应变慢
RT(响应时间)从毫秒级增加到秒级
P99/P95 延迟显著上升
吞吐量下降
用户体验恶化
雪崩风险
单点故障扩散到其他节点
整个集群性能下降
可能导致服务不可用
1.3 常见症状 🔍 监控指标异常 :
CPU 使用率突增
1 2 3 4 5 CPU Usage: 20%-40% CPU Usage: 80%-100%
Load Average 飙升
1 2 3 4 5 load average: 1.23, 0.98, 0.76 load average: 15.67, 12.34, 10.89
线程状态异常
RUNNABLE 线程数量激增
BLOCKED/WAITING 线程堆积
GC 频率增加
二、排查思路 2.1 总体流程 采用六步法 系统化排查:
1 2 3 4 止血 → 定位 → 分析 → 修复 → 验证 → 复盘 ↓ ↓ ↓ ↓ ↓ ↓ 控制 找到 深挖 解决 确认 改进 影响 问题 根因 方案 效果 预防
每一步的核心目标 :
阶段
目标
关键动作
止血
快速恢复服务
限流、降级、扩容、重启
定位
找到高 CPU 线程
top、jstack、Arthas
分析
确定根本原因
线程状态、代码逻辑、GC 情况
修复
解决问题
代码优化、JVM 调优、架构改进
验证
确认效果
灰度发布、监控观察
复盘
防止复发
总结经验、完善监控、优化规范
2.2 核心原则 ✅ 排查原则 :
先止血后分析
无损排查
线上不盲目执行危险命令
优先使用监控大盘、Arthas 等无损工具
数据驱动
基于监控数据判断,不凭感觉
保留现场证据(线程 dump、GC 日志)
闭环思维
排查不是目的,预防才是关键
建立基线监控、压测标准、容量规划
三、止血阶段 3.1 快速定位故障节点 通过监控大盘迅速定位问题节点。
监控指标 :
1. CPU 使用率分布 1 2 3 4 node_cpu_seconds_total{mode="user" } node_cpu_seconds_total{mode="system" } node_cpu_seconds_total{mode="iowait" }
分析要点 :
User CPU 高 :业务计算密集(死循环、复杂算法)
Sys CPU 高 :系统调用频繁(上下文切换、锁竞争)
Wait CPU 高 :I/O 阻塞(磁盘、网络)
2. Load Average
判断标准 :
Load Average < CPU 核数:正常
Load Average ≈ CPU 核数:繁忙
Load Average > CPU 核数 × 2:过载
3.2 系统负载分析 结合 vmstat 深入分析系统状态。
vmstat 命令 :
1 2 3 4 5 6 7 8 vmstat 1 5 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 8 0 0 102400 51200 204800 0 0 100 200 5000 8000 85 10 3 2 0 9 0 0 100000 51200 204800 0 0 120 250 6000 9000 90 8 1 1 0
关键字段解读 :
字段
含义
正常值
异常说明
r
运行队列长度
< CPU 核数
过大说明 CPU 瓶颈
b
阻塞进程数
0-2
过小说明 I/O 瓶颈
si/so
交换内存
0
非 0 说明物理内存不足
in
中断次数
-
过高说明硬件中断频繁
cs
上下文切换
< 10000
过高说明线程竞争激烈
us
用户态 CPU%
20-60%
过高说明业务计算密集
sy
内核态 CPU%
5-15%
过高说明系统调用频繁
id
空闲 CPU%
20-50%
过低说明 CPU 资源紧张
wa
I/O 等待%
0-5%
过高说明 I/O 瓶颈
异常判断 :
❌ cs > 10000:上下文切换过于频繁
❌ sy > 20%:系统调用过多
❌ wa > 10%:I/O 成为瓶颈
❌ si/so > 0:发生 swap,内存不足
3.3 紧急处理措施 根据问题严重程度采取相应措施。
措施优先级 :
1. 网关限流/降级(首选) 1 2 3 4 5 6 7 8 9 10 11 rules: - resource: /api/order/create grade: 1 count: 1000 strategy: 0 - resource: /api/report/export grade: 0 count: 10 strategy: 1
降级策略 :
2. 动态扩容 1 2 3 4 5 kubectl scale deployment my-app --replicas=10 docker service scale my_app=10
注意事项 :
确保新节点能快速启动
检查依赖服务容量(DB、Redis)
监控扩容后整体负载
3. 摘除故障节点并重启(最后手段) 1 2 3 4 5 6 7 8 9 upstream backend { server 192.168.1.10:8080 down; server 192.168.1.11:8080; server 192.168.1.12:8080; } systemctl restart my-app
风险提示 :
⚠️ 重启会丢失现场信息
⚠️ 仅在必要时使用
⚠️ 重启前尽量保存线程 dump
四、定位阶段 4.1 传统方式:top + jstack 经典的四步定位法。
步骤 1:找高 CPU 进程
操作 :
按 P 键按 CPU 排序
记录 Java 进程的 PID(如 12345)
按 q 退出
步骤 2:找该进程下高 CPU 线程 1 2 3 4 5 6 7 8 9 10 11 12 top -Hp 12345 pidstat -t -p 12345 1 5 Linux 5.4.0 (server01) 01/04/26 _x86_64_ (8 CPU) Average: UID TGID TID %usr %system %guest %CPU CPU Command Average: 1000 - 12346 85.20 2.30 0.00 87.50 2 java Average: 1000 - 12347 10.50 1.20 0.00 11.70 3 java
操作 :
记录占用最高的 TID(如 12346)
关注 %usr(用户态)和 %system(内核态)
步骤 3:TID 转 16 进制
说明 :
Linux 线程 ID(TID)是十进制
jstack 输出中的线程 ID 是十六进制
需要转换后才能匹配
步骤 4:抓取线程堆栈 1 2 3 4 5 6 7 8 9 jstack 12345 | grep "303a" -A 20 jstack 12345 > /tmp/jstack_$(date +%Y%m%d_%H%M%S).txt vim /tmp/jstack_20260104_120000.txt /303a
输出示例 :
1 2 3 4 5 6 "http-nio-8080-exec-10" #42 daemon prio=5 os_prio=0 tid=0x00007f8b4c001000 nid=0x303a runnable [0x00007f8b2c5fe000] java.lang.Thread.State: RUNNABLE at com.example.service.OrderService.calculatePrice(OrderService.java:125) at com.example.controller.OrderController.createOrder(OrderController.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ...
分析要点 :
查看线程状态(RUNNABLE/BLOCKED/WAITING)
定位到具体类和方法
查看调用栈上下文
4.2 现代方式:Arthas 阿里巴巴开源的 Java 诊断工具,更高效便捷。
安装 Arthas :
1 2 3 4 5 curl -O https://arthas.aliyun.com/arthas-boot.jar java -jar arthas-boot.jar
快速定位高 CPU 线程 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 thread -n 5 "http-nio-8080-exec-10" Id=42 RUNNABLE cpu usage: 87.5% at com.example.service.OrderService.calculatePrice(OrderService.java:125) at com.example.controller.OrderController.createOrder(OrderController.java:45) ... "http-nio-8080-exec-5" Id=37 RUNNABLE cpu usage: 10.2% at java.util.HashMap.hash(HashMap.java:339) ...
优势 :
✅ 无需手动转换 TID
✅ 直接显示 CPU 使用率
✅ 自动排序,一目了然
✅ 支持在线诊断,无需重启
其他有用命令 :
1 2 3 4 5 6 7 8 9 10 11 12 13 thread thread 42 thread -n 5 -i 1000 profiler start profiler stop --format html
4.3 高级方式:火焰图 直观展示 CPU 热点方法链,比纯看 jstack 更高效。
工具选型 :
工具
优势
适用场景
async-profiler
低开销、支持 perf events
生产环境首选
Arthas profiler
集成在 Arthas 中,易用
快速诊断
JProfiler
图形化界面,功能强大
本地开发调试
使用 async-profiler :
1 2 3 4 5 6 7 8 9 10 wget https://github.com/async-profiler/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gz tar -xzf async-profiler-2.9-linux-x64.tar.gz cd async-profiler-2.9-linux-x64./profiler.sh -d 30 -f /tmp/flamegraph.html 12345 ./profiler.sh -d 30 -f /tmp/flamegraph.svg 12345
使用 Arthas profiler :
1 2 3 4 5 6 7 8 9 profiler start profiler stop --format html ls -lh /tmp/arthas-output/
火焰图解读 :
1 2 3 4 5 6 7 8 9 10 11 12 13 每个方块代表一个方法: - 宽度:CPU 时间占比(越宽越热点) - 高度:调用栈深度 - 颜色:随机分配(便于区分) 从上往下看: - 顶层:入口方法 - 底层:叶子方法(实际执行代码) 从下往上看: - 找到最宽的方块(CPU 热点) - 向上追溯调用链 - 定位到具体业务代码
优势 :
✅ 直观展示 CPU 热点
✅ 快速定位瓶颈方法
✅ 支持离线分析
✅ 开销极低(< 1%)
五、分析阶段 5.1 线程状态分析 根据线程状态判断问题类型。
线程状态分类 :
状态
说明
典型场景
RUNNABLE
正在运行或可运行
死循环、复杂计算、频繁 GC
BLOCKED
等待获取锁
锁竞争、 synchronized 阻塞
WAITING
无限期等待
Object.wait()、Thread.join()
TIMED_WAITING
限时等待
Thread.sleep()、Object.wait(timeout)
5.2 常见原因分类 1. RUNNABLE 状态 - CPU 密集型 典型原因 :
原因
说明
示例
死循环
while(true) 无条件退出
while(true) { doSomething(); }
复杂正则
回溯爆炸
Pattern.compile("(a+)+b")
大量计算
数学运算、加密解密
RSA 签名、哈希计算
序列化/反序列化
JSON/XML 解析大对象
Jackson 解析 10MB JSON
频繁 Full GC
Stop-The-World 占用 CPU
老年代内存泄漏
验证手段 :
1 2 3 4 5 6 7 8 jstat -gcutil 12345 1000 S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 45.23 67.89 89.12 92.34 88.56 1234 12.345 56 45.678 58.023 ↑ FGC 频繁且耗时长
判断标准 :
FGC(Full GC 次数)快速增长
FGCT(Full GC 总耗时)占比高
O(Old Gen)使用率持续高位
2. BLOCKED/WAITING 状态 - 锁竞争/I/O 阻塞 典型原因 :
原因
说明
示例
线程锁竞争
synchronized/ReentrantLock
热点数据并发更新
连接池耗尽
DB/Redis 连接不足
HikariCP maximumPoolSize 过小
同步 I/O 阻塞
网络/磁盘 I/O 等待
HTTP 请求超时、文件读写
验证手段 :
1 2 3 4 5 6 7 8 9 jstack 12345 | grep "waiting to lock" -B 5 -A 10 "http-nio-8080-exec-10" java.lang.Thread.State: BLOCKED (on object monitor) at com.example.service.OrderService.updateStock(OrderService.java:89) - waiting to lock <0x00000000f5e8a123> (a com.example.model.Product) at com.example.controller.OrderController.createOrder(OrderController.java:45)
数据库慢查询检查 :
1 2 3 4 5 6 SHOW PROCESSLIST ;SELECT * FROM information_schema.processlist WHERE TIME > 5 ;SLOWLOG GET 10
3. Sys CPU 占比高 - 系统调用频繁 典型原因 :
原因
说明
示例
频繁上下文切换
线程数过多
线程池大小不合理
大量短连接
TCP 连接频繁创建销毁
HTTP 短连接未复用
锁自旋
CAS 操作失败重试
AQS 自旋锁
驱动/网络中断
硬件中断处理
网卡驱动问题
验证手段 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pidstat -w 1 5 Linux 5.4.0 (server01) 01/04/26 _x86_64_ (8 CPU) Average: UID PID cswch/s nvcswch/s Command Average: 1000 12345 8500.00 200.00 java ↑ 自愿上下文切换过高 sar -n DEV 1 5 cat /proc/sys/net/core/somaxconn cat /proc/sys/net/ipv4/tcp_tw_recycle
判断标准 :
cswch/s(自愿上下文切换)> 10000:线程竞争激烈
nvcswch/s(非自愿上下文切换)> 1000:CPU 资源不足
5.3 验证手段 综合分析多种指标,确认根本原因。
综合检查清单 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 jstack 12345 | grep "java.lang.Thread.State" | sort | uniq -c 85 RUNNABLE 10 BLOCKED 5 WAITING 50 TIMED_WAITING jstat -gcutil 12345 1000 5 vmstat 1 5 netstat -an | grep ESTABLISHED | wc -l lsof -p 12345 | wc -l
六、修复方案 6.1 代码层优化 针对不同的根本原因采取相应措施。
1. 修复死循环 问题代码 :
1 2 3 4 5 6 7 8 9 10 public void processData () { while (true ) { Data data = queue.poll(); if (data == null ) { continue ; } process(data); } }
修复方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void processData () { while (!shutdown) { try { Data data = queue.poll(1 , TimeUnit.SECONDS); if (data != null ) { process(data); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break ; } } }
2. 优化复杂正则 问题代码 :
1 2 3 4 5 String regex = "(a+)+b" ; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher("aaaaaaaaaaaaaaaaX" ); matcher.matches();
修复方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 String regex = "a+b" ; Pattern pattern = Pattern.compile(regex); ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Boolean> future = executor.submit(() -> { return pattern.matcher(input).matches(); }); try { Boolean result = future.get(1 , TimeUnit.SECONDS); } catch (TimeoutException e) { future.cancel(true ); log.warn("正则匹配超时" ); }
3. 改用异步处理 问题代码 :
1 2 3 4 5 6 @GetMapping ("/export" )public void exportReport (HttpServletResponse response) throws IOException { List<Order> orders = orderService.findAll(); ExcelExporter.export(response.getOutputStream(), orders); }
修复方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @PostMapping ("/export" )public String exportReport (@RequestParam String queryId) { CompletableFuture.runAsync(() -> { int pageSize = 1000 ; int pageNum = 1 ; while (true ) { Page<Order> page = orderService.findByPage(queryId, pageNum, pageSize); if (page.isEmpty()) break ; ExcelExporter.appendToFile("/tmp/export.xlsx" , page.getContent()); pageNum++; } notificationService.sendDownloadLink(queryId, "/tmp/export.xlsx" ); }); return "导出任务已提交,完成后将通知您" ; }
4. 优化算法复杂度 问题代码 :
1 2 3 4 5 6 7 8 9 10 11 12 public List<User> findDuplicates (List<User> users) { List<User> duplicates = new ArrayList<>(); for (int i = 0 ; i < users.size(); i++) { for (int j = i + 1 ; j < users.size(); j++) { if (users.get(i).getEmail().equals(users.get(j).getEmail())) { duplicates.add(users.get(i)); } } } return duplicates; }
修复方案 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public List<User> findDuplicates (List<User> users) { Map<String, User> emailMap = new HashMap<>(); Set<User> duplicates = new HashSet<>(); for (User user : users) { User existing = emailMap.put(user.getEmail(), user); if (existing != null ) { duplicates.add(existing); } } return new ArrayList<>(duplicates); }
6.2 JVM 层调优 调整 JVM 参数优化性能。
1. 调整 GC 参数 G1 GC 优化 :
1 2 3 4 5 6 7 8 9 10 11 12 13 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m -XX:InitiatingHeapOccupancyPercent=45 -XX:G1NewSizePercent=20 -XX:G1MaxNewSizePercent=40 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=2
调优建议 :
✅ MaxGCPauseMillis 不宜过小(会导致频繁 GC)
✅ 根据实际负载调整 ParallelGCThreads
✅ 监控 GC 日志验证效果
2. 调大年轻代减少 Minor GC 问题 :Minor GC 过于频繁
1 2 3 4 jstat -gcutil 12345 1000
修复 :
1 2 3 4 -Xmn2g -XX:NewRatio=2
3. 排查内存泄漏 如果 Full GC 频繁且回收效果差,可能是内存泄漏。
排查步骤 :
1 2 3 4 5 6 7 8 9 jcmd 12345 GC.heap_dump /tmp/heapdump.hprof
参考 :详见 JVM 内存泄漏面试题
6.3 架构层改进 从系统架构层面优化性能。
1. 日志改异步 问题 :同步日志 I/O 阻塞
1 2 3 4 5 6 <Appenders > <RollingFile name ="file" fileName ="app.log" > <PatternLayout pattern ="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n" /> </RollingFile > </Appenders >
修复 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <Appenders > <Async name ="async" > <AppenderRef ref ="file" /> </Async > <RollingFile name ="file" fileName ="app.log" > <PatternLayout pattern ="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n" /> </RollingFile > </Appenders > <Loggers > <Root level ="info" > <AppenderRef ref ="async" /> </Root > </Loggers >
性能提升 :
✅ 日志写入不阻塞业务线程
✅ 吞吐量提升 20%-50%
2. 报表走离线数仓 问题 :实时查询大数据量报表
1 2 3 4 5 6 @GetMapping ("/report/sales" )public SalesReport getSalesReport (@RequestParam String date) { List<Order> orders = orderRepository.findByDate(date); return salesAnalyzer.analyze(orders); }
修复 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @GetMapping ("/report/sales" )public SalesReport getSalesReport (@RequestParam String date) { String cacheKey = "sales_report:" + date; SalesReport report = redisTemplate.get(cacheKey); if (report == null ) { report = dataWarehouse.querySalesReport(date); redisTemplate.setex(cacheKey, 3600 , report); } return report; } @Scheduled (cron = "0 0 2 * * ?" )public void precomputeReports () { String yesterday = LocalDate.now().minusDays(1 ).toString(); SalesReport report = dataWarehouse.querySalesReport(yesterday); redisTemplate.setex("sales_report:" + yesterday, 86400 * 7 , report); }
3. 引入缓存/读写分离 缓存优化 :
1 2 3 4 5 6 7 8 9 10 11 private static final Cache<String, Product> productCache = Caffeine.newBuilder() .maximumSize(10_000 ) .expireAfterWrite(10 , TimeUnit.MINUTES) .build(); public Product getProduct (Long id) { return productCache.get(id.toString(), key -> productRepository.findById(Long.parseLong(key)) ); }
读写分离 :
1 2 3 4 5 6 7 8 9 spring: datasource: master: url: jdbc:mysql://master:3306/mydb driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://slave:3306/mydb driver-class-name: com.mysql.cj.jdbc.Driver
1 2 3 4 5 6 7 8 9 10 11 12 13 @Transactional (readOnly = true )@DataSource ("slave" )public List<Order> queryOrders () { return orderRepository.findAll(); } @Transactional @DataSource ("master" )public Order createOrder (Order order) { return orderRepository.save(order); }
七、验证与预防 7.1 灰度发布验证 采用渐进式发布,降低风险。
发布流程 :
1 2 3 4 5 第 1 天:10% 节点 → 观察核心指标 ↓ 无异常 第 2-3 天:30% 节点 → 继续观察 ↓ 无异常 第 4-7 天:100% 节点 → 全量发布
观察指标 :
指标
正常范围
告警阈值
CPU 使用率
20%-60%
> 80% 持续 3 分钟
RT(P99)
< 500ms
> 1000ms
QPS
波动 < 20%
下降 > 30%
错误率
< 0.1%
> 1%
Full GC 频率
< 10 次/天
> 1 次/小时
回滚预案 :
如果 CPU 再次飙高 → 立即回滚
如果 RT 显著增加 → 立即回滚
保留旧版本镜像至少 7 天
7.2 监控告警配置 建立完善的监控告警体系。
Prometheus 告警规则 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 groups: - name: cpu-alerts rules: - alert: HighCpuUsage expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 ) > 80 for: 3m labels: severity: warning annotations: summary: "{{ $labels.instance }} CPU 使用率超过 80%" description: "当前 CPU 使用率: {{ $value }} %" - alert: HighLoadAverage expr: node_load1 > on(instance) node_cpu_info{cpu="cpu0"} * 2 for: 5m labels: severity: critical annotations: summary: "{{ $labels.instance }} Load Average 过高" description: "1 分钟 Load: {{ $value }} " - alert: HighContextSwitches expr: rate(node_context_switches_total[5m]) > 10000 for: 5m labels: severity: warning annotations: summary: "{{ $labels.instance }} 上下文切换过于频繁" description: "上下文切换速率: {{ $value }} /s"
线程池监控 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Component public class ThreadPoolMetrics { @Autowired private MeterRegistry meterRegistry; @PostConstruct public void init () { Gauge.builder("threadpool.active.count" , threadPoolExecutor::getActiveCount) .register(meterRegistry); Gauge.builder("threadpool.queue.size" , () -> threadPoolExecutor.getQueue().size()) .register(meterRegistry); } }
7.3 复盘总结 输出完整的复盘报告。
复盘报告模板 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 # CPU 飙高故障复盘报告 ## 1. 故障概述 - **发生时间** :2026-01-04 10:00 - 12:00- **影响范围** :订单服务 3/10 节点- **影响时长** :2 小时- **影响用户** :约 5000 用户## 2. 根因分析 - **直接原因** :订单导出接口存在死循环 bug- **根本原因** :Code Review 未发现边界条件问题- **触发条件** :当队列为空时,while(true) 无条件循环## 3. 处理过程 - 10:05 监控告警 CPU > 90%- 10:10 定位到高 CPU 线程- 10:15 摘除故障节点- 10:30 修复代码并发布- 11:00 灰度验证通过- 12:00 全量发布完成## 4. 改进项 - [ ] 修复死循环 bug(已完成)- [ ] 增加单元测试覆盖边界条件(进行中)- [ ] Code Review checklist 增加死循环检查(计划中)- [ ] 配置 CPU 使用率告警(已完成)- [ ] 建立压测基线(计划中)## 5. 经验教训 - 所有 while 循环必须有明确的退出条件- 队列消费建议使用阻塞队列的 poll(timeout)- 加强 Code Review,重点关注循环和递归
八、面试回答模板 8.1 标准回答框架 面试官问:“线上服务 CPU 飙高,你如何排查?请描述完整的调优流程”
回答框架 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 我会按照「止血 → 定位 → 分析 → 修复 → 验证 → 复盘」六步法来处理: 第一步【止血】: 首先通过监控大盘快速定位故障节点,查看 CPU 使用率分布(user/sys/wait)、 Load Average、vmstat 上下文切换等指标。然后采取紧急措施:网关限流/降 级非核心链路、动态扩容,必要时摘除故障节点并重启,优先恢复服务可用性。 第二步【定位】: 精确定位高 CPU 线程。传统方式是 top 找进程 → top -Hp 找线程 → printf 转 16 进制 → jstack 抓堆栈。更高效的方式是使用 Arthas 的 thread -n 5 直接找出 CPU 最高的线程。如果需要深入分析,可以使用 async-profiler 生 成火焰图,直观看到 CPU 热点方法链。 第三步【分析】: 根据线程状态分析原因: - RUNNABLE:可能是死循环、复杂正则、大量计算、频繁 Full GC - BLOCKED/WAITING:可能是锁竞争、连接池耗尽、I/O 阻塞 - Sys CPU 高:可能是频繁上下文切换、大量短连接 通过 jstat 查看 GC 频率,jstack 查看锁竞争,vmstat 查看上下文切换, 综合判断根本原因。 第四步【修复】: 针对不同原因采取对应措施: - 代码层:修复死循环/正则、改用异步处理、优化算法复杂度 - JVM 层:调整 GC 参数、调大年轻代、排查内存泄漏 - 架构层:日志改异步、报表走离线数仓、引入缓存/读写分离 第五步【验证】: 采用灰度发布策略(10% → 30% → 100%),持续观察 3-7 天。重点关注 CPU 使用率、RT、QPS、错误率曲线是否正常,Full GC 频率是否恢复到 正常水平。 第六步【复盘】: 输出复盘报告,包括根因、影响面、改进项。补充监控告警(CPU > 80% 持 续 3 分钟触发告警),配置线程池/连接池监控。完善 Code Review 规范, CI 集成静态代码扫描,建立压测基线和容量规划,形成工程化闭环。
8.2 加分项 展示深度思考 :
提到具体工具链
1 2 3 "我们团队使用 Prometheus + Grafana 监控 CPU 指标, 配合 Arthas 在线诊断,async-profiler 生成火焰图, 形成完整的诊断链路。"
提到实际案例
1 2 3 "我之前遇到过订单导出接口的死循环问题,原因是队列 为空时 while(true) 没有退出条件。通过在 poll 时设 置超时时间解决,同时增加了队列监控告警。"
提到性能权衡
1 2 3 4 "在修复时要注意性能影响,比如: - 异步日志提升了吞吐量,但可能丢失少量日志 - 增大年轻代减少了 Minor GC,但增加了 Full GC 风险 - 需要在稳定性和性能之间找到平衡点"
提到预防措施
1 2 3 4 5 "除了事后处理,我们更注重事前预防: - Code Review 强制检查循环和递归的退出条件 - CI 集成 SonarQube 检测复杂度和潜在死循环 - 压测时专门监控 CPU 增长趋势 - 定期(每季度)进行性能专项排查"
九、常见问题 9.1 Load Average vs CPU Usage 区别 :
对比项
Load Average
CPU Usage
定义
单位时间内运行队列中的平均进程数
CPU 使用百分比
包含状态
R(运行)+ D(不可中断睡眠)
仅 CPU 使用时间
受 I/O 影响
✅ 是(D 状态也计入)
❌ 否
适用场景
综合负载评估
CPU 瓶颈判断
重要提醒 :
⚠️ Load 高不一定是 CPU 问题,可能是 D 状态进程(I/O 阻塞)
✅ 需要结合 vmstat 的 wa(I/O wait)字段判断
✅ 如果 wa 高,说明是 I/O 瓶颈,不是 CPU 瓶颈
判断方法 :
1 2 3 4 5 6 7 load average: 15.67, 12.34, 10.89 vmstat: wa = 30% load average: 15.67, 12.34, 10.89 vmstat: us = 90%
9.2 生产安全注意事项 ✅ 安全排查原则 :
不盲目执行危险命令
1 2 3 4 5 kill -3 <PID>java -jar arthas-boot.jar
不频繁执行 jstack
1 2 3 4 5 while true ; do jstack <PID>; done jstack <PID> > /tmp/jstack_$(date +%Y%m%d_%H%M%S).txt
优先使用无损工具
✅ Prometheus + Grafana 监控大盘
✅ Arthas 在线诊断
✅ pidstat、vmstat 系统工具
❌ 谨慎使用 jmap、jstack(会暂停 JVM)
保留现场证据
1 2 3 4 5 jstack <PID> > /tmp/jstack.txt jstat -gcutil <PID> 1000 10 > /tmp/gc.txt vmstat 1 10 > /tmp/vmstat.txt top -Hp <PID> -b -n 1 > /tmp/top.txt
参考资料