news 2026/4/16 22:04:23

【JVM深度解析】第16篇:JVM配置优化案例三:CPU 100%排查(线程死循环)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JVM深度解析】第16篇:JVM配置优化案例三:CPU 100%排查(线程死循环)

摘要

CPU 使用率 100% 是线上最紧急的故障之一——服务响应变慢、接口超时、用户体验断崖式下降。本案例记录一次完整的 CPU 100% 问题排查全流程:从top -Hp定位高占用线程,到jstack获取线程快照,再到用printf '%x'转换线程 ID,最终在火焰图中精确定位到OrderMatcher.match()方法中的一个正则表达式预编译遗漏导致死循环。修复后 CPU 从 100% 降到 15%,服务恢复正常。文中还介绍了Async-profilerArthas profiler两种高级火焰图工具的使用方法。


一、问题背景

1.1 故障场景

某金融交易系统,运行在 8 核 16GB 的服务器上,JDK 11 + G1 GC。系统在下午 14:00 突然出现异常:

监控告警: [14:01] CPU 使用率告警:当前 98%(阈值 80%) [14:02] 接口 TP99 响应时间:> 30s(正常 < 500ms) [14:03] 服务开始拒绝请求,线程池耗尽 [14:05] 触发自动扩容,新 Pod 启动后同样 CPU 100% 运维初步排查: $ top Cpu(s): 100.0%us, 0.0%sy → 用户态 CPU 100%,不是系统调用 $ free -h Mem: 15Gi total → 内存充足,不是 GC 问题

1.2 初步分析

CPU 100% 的常见原因: ┌──────────────────────────────────────────────────────────────────┐ │ 原因分类 │ 典型场景 │ 排查方向 │ ├──────────────────┼──────────────────────────┼──────────────────┤ │ 死循环 │ while(true) 无限循环 │ jstack + 线程状态 │ │ 密集计算 │ 大数据排序/加密/压缩 │ 火焰图定位热点 │ │ 正则回溯 │ 灾难性回溯(Catastrophic) │ 火焰图 + OQL │ │ JIT 热编译 │ 新代码路径频繁触发编译 │ 预热 + 分层编译 │ │ GC 密集 │ Metaspace 大量加载 │ GC 日志分析 │ └──────────────────────────────────────────────────────────────────┘ 排除法: - 内存充足 → 不是堆问题 - CPU 100%us → 不是系统调用 - 突然发生 → 不是正常的负载增长 → 最大可能是死循环或正则回溯

二、排查流程

2.1 Step 1:找到高 CPU 线程

# 查看 Java 进程的线程 CPU 使用$top-Hp12345# 输出(截取高 CPU 线程):# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND# 12456 app 20 0 16.0g 2.5g 50m R 95.5 16.0 12:34.56 java# 12458 app 20 0 16.0g 2.5g 50m R 88.2 16.0 9:12.34 java# 12460 app 20 0 16.0g 2.5g 50m R 45.1 16.0 3:45.67 java# 12478 app 20 0 16.0g 2.5g 50m R 2.5 16.0 0:12.34 java# 找到占用 CPU 最高的线程 PID:12456

2.2 Step 2:转换线程 ID

# 将 PID 转换为十六进制(jstack 输出的线程 ID 是十六进制)$printf'%x\n'124563038$printf'%x\n'12458303a

2.3 Step 3:获取线程快照

# 获取完整线程快照$ jstack12345>/tmp/threaddump_$(date+%Y%m%d_%H%M%S).txt# 搜索高 CPU 线程$grep-A50"0x3038"/tmp/threaddump_20260315_140500.txt# 输出:"pool-1-thread-15"#12345 prio=5 os_prio=0 tid=0x00007f1234567890java.lang.Thread.State: RUNNABLE at com.example.OrderMatcher.match(OrderMatcher.java:45)at com.example.OrderService.processOrder(OrderService.java:123)at com.example.OrderController.handle(OrderController.java:67)at sun.reflect.GeneratedMethodAccessor45.invoke(Unknown Source)...

2.4 Step 4:定位问题代码

// 定位到 OrderMatcher.java:45// 打开源码查看第 45 行publicclassOrderMatcher{publicList<Order>match(Stringsymbol,Stringpattern){List<Order>result=newArrayList<>();Patternp=Pattern.compile(pattern);// ← 问题!每次调用都编译for(Orderorder:allOrders){if(p.matcher(order.getSymbol()).matches()){// ← 第 45 行result.add(order);}}returnresult;}}// 问题分析:// - 传入的 pattern = ".*(A+|B+|C+)+.*" (灾难性回溯正则)// - Pattern.compile() 每秒调用 10000 次 → 每次都重新编译// - 正则表达式灾难性回溯 → 导致 CPU 100%

三、根因分析

3.1 正则表达式灾难性回溯

灾难性回溯(Catastrophic Backtracking)示例: 正则:.*(A+|B+|C+)+.* 输入:"AAAAAAAAAAAAAAAAAX"(19 个 A + X) 匹配过程: - .* 贪婪匹配所有字符 - 然后回溯,发现 (A+)+ 需要匹配 - A+ 尝试匹配 19 个 A - (A+)+ 继续回溯,再次尝试匹配 A+ - 重复 19 次,指数级增长 - 总尝试次数 ≈ 2^19 = 524,288 次! 这就是正则回溯导致死循环的原理。

3.2 问题代码完整版

// 问题代码publicclassOrderMatcher{// 问题1:每次调用都编译正则(浪费 CPU)// 问题2:用户输入的正则可能是灾难性回溯publicList<Order>match(Stringsymbol,Stringpattern){Patternp=Pattern.compile(pattern);// 危险!returnallOrders.stream().filter(o->p.matcher(o.getSymbol()).matches()).collect(Collectors.toList());}}// 调用链(从日志中还原)// orderMatch.match("AAPL", ".*(A+|B+|C+)+.*")// ↑↑↑ 用户查询传入// 恶意或低效正则

四、解决方案

4.1 紧急修复:预编译正则 + 超时

// 修复方案 1:预编译正则 + 简化正则publicclassOrderMatcher{// 预编译常用正则模式privatestaticfinalMap<String,Pattern>PATTERN_CACHE=Caffeine.newBuilder().maximumSize(100).expireAfterWrite(Duration.ofMinutes(5)).build().asMap();// 使用简单通配符替代正则publicList<Order>match(Stringsymbol,Stringpattern){Patternp=PATTERN_CACHE.computeIfAbsent(pattern,Pattern::compile);// 添加超时保护longstart=System.nanoTime();for(Orderorder:allOrders){if(p.matcher(order.getSymbol()).matches()){result.add(order);}// 超时 1 秒则退出if(System.nanoTime()-start>1_000_000_000){thrownewOrderMatchTimeoutException("Pattern match timeout");}}returnresult;}}

4.2 根本解决:限制用户输入的正则

// 修复方案 2:用户输入只允许简单通配符,不允许完整正则publicclassOrderMatcher{publicList<Order>match(Stringsymbol,StringwildcardPattern){// 将通配符转换为正则(安全)Stringregex=wildcardToRegex(wildcardPattern);Patternp=Pattern.compile(regex);returnallOrders.stream().filter(o->p.matcher(o.getSymbol()).matches()).collect(Collectors.toList());}// 安全的通配符转换privateStringwildcardToRegex(Stringwildcard){// 只允许 * 和 ? 通配符,不支持正则特殊字符return"^"+wildcard.replace(".","\\.").replace("*",".*").replace("?",".")+"$";}}

4.3 上线配置

# 上线后 JVM 配置(添加 CPU 限制保护)JAVA_OPTS=" -XX:+UseG1GC -XX:MaxGCPauseMillis=200 # 如果问题没有彻底解决,设置 CPU 限制 # -XX:ActiveProcessorCount=4 # 限制使用 4 核 "

五、效果验证

5.1 修复前后对比

# 修复前 CPU$top-bn1|grepjavaCpu(s):100.0%us,0.0%sy → CPU100%# 修复后 CPU$top-bn1|grepjavaCpu(s):15.2%us,0.8%sy → CPU 约16%# 修复前后对比┌──────────────────────────────────────────────────────────────────┐ │ 指标 │ 修复前 │ 修复后 │ 改善 │ ├────────────────────┼─────────────┼─────────────┼────────────┤ │ CPU 使用率 │100% │15% │85% ↓ │ │ 接口 TP99 │>30s │<500ms │99% ↓ │ │ 服务可用性 │20% │99.9% │80% ↑ │ │ 每秒请求处理数 │52000│ 400x ↑ │ └────────────────────┴─────────────┴─────────────┴────────────┘

5.2 监控图表

CPU 使用率变化: ↑ 100% ─┐ │ │ ┌──────────────────修复点 │ │ │ │ └──────┐ ↓ │ └──────→ CPU 降到 15% │ └──────────────────────────────────────────────────────→ 时间

六、高级工具:火焰图

6.1 Async-profiler 火焰图

# 安装 async-profilerwgethttps://github.com/async-profiler/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-x64.tar.gztar-xzfasync-profiler-2.9-linux-x64.tar.gz# 生成 CPU 火焰图(30 秒)./profiler.sh-d30-f/tmp/cpu.svg-ecpu12345# 生成内存分配火焰图./profiler.sh-d30-f/tmp/alloc.svg-ealloc12345# 查看火焰图(上传到浏览器打开)# 火焰图解读:顶部越宽 = CPU 占用越多
火焰图解读: ┌──────────────────────────────────────────────────────────────────┐ │ [全部] cpu │ │ 100% │ │ │ │ │ ┌─────────┴─────────┐ │ │ OrderMatcher.match() 80% │ │ │ │ │ ┌───────┴───────┐ │ │ Pattern.compile() Pattern.matcher() 75% │ │ │ │ 火焰图顶部越宽 = 该方法占用 CPU 越多 │ │ 从上往下看 = 完整的调用栈 │ │ │ └──────────────────────────────────────────────────────────────────┘

6.2 Arthas profiler

# 使用 Arthas 生成火焰图# 1. 启动 Arthasjava-jararthas-boot.jar# 2. 连接后执行$ profiler start Started[cpu]profiling# 3. 运行一段时间后$ profiler stop--formathtml>/tmp/profile.html# 4. 查看火焰图$cat/tmp/profile.html|head-50

七、经验总结

7.1 CPU 100% 排查流程图

┌──────────────────────────────────────────────────────────────────┐ │ CPU 100% 排查流程 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 1. top -Hp <pid> │ │ └→ 找到 CPU 最高的线程 PID │ │ │ │ 2. printf '%x\n' <pid> │ │ └→ 转换为十六进制 │ │ │ │ 3. jstack <pid> | grep <hex_pid> │ │ └→ 查看该线程的堆栈,找到 RUNNABLE 方法 │ │ │ │ 4. 打开源码定位问题 │ │ └→ 死循环 / 正则回溯 / 密集计算 │ │ │ │ 5. 修复代码,重新上线 │ │ │ │ 备选工具: │ │ - Arthas: profiler start/stop │ │ - async-profiler: ./profiler.sh -f cpu.svg <pid> │ │ │ └──────────────────────────────────────────────────────────────────┘

7.2 预防措施

代码审查 checklist: 1. 正则表达式必须预编译(Pattern.compile 放在 static/类初始化中) 2. 用户输入的正则需要白名单验证 3. 大数据量循环需要分批或超时保护 4. 加密/压缩等 CPU 密集操作需要限流

系列导航

  • 上一篇:【JVM深度解析】第15篇:JVM配置优化案例二:内存泄漏定位与修复
  • 下一篇:【JVM深度解析】第17篇:JVM配置优化案例四:线程死锁与接口超时诊断
  • 系列目录:JVM深度解析系列全集

参考资料

  1. Async-profiler GitHub
  2. Arthas Profiler
  3. FlameGraph
  4. 正则表达式灾难性回溯
  5. Netflix CPU Flame Graph Guide
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 22:03:59

读原问者《知行合一:王阳明心学智慧讲习》,悟落地心学之道

在信息纷繁、人心浮躁的当下&#xff0c;王阳明心学已成为许多人寻求内心安定与人生方向的精神依托。“致良知”“知行合一” 广为流传&#xff0c;可真正将心学融入日用、化为行动的人却寥寥无几。多数人或停留在书本空谈&#xff0c;或仅将其作为装点门面的谈资&#xff0c;让…

作者头像 李华
网站建设 2026/4/16 21:59:04

Avalonia跨平台入门第八十七篇之热点配网与设备发现

前面简单玩了一下Avalonia跨平台入门第二十九篇之再玩GIS、离线地图、离线深色地图、GIS加载GeoJson和Shp、加载热力图、GIS聚合效果、WMS图层点击查询与自定义弹窗、路径轨迹回放、GIS建筑伪3D效果、GIS路径规划、GIS基础图形绘制、GIS鹰眼图、集合拖放到TreeView、TreeView节…

作者头像 李华
网站建设 2026/4/16 21:59:02

Labview机器视觉三件套(VDM+VAS)保姆级安装避坑指南,一次搞定不报错

LabVIEW机器视觉三件套&#xff08;VDMVAS&#xff09;零基础安装实战指南 第一次接触LabVIEW机器视觉套件时&#xff0c;我被各种版本兼容性问题折磨得焦头烂额——明明按照官方文档操作&#xff0c;却总是弹出各种莫名其妙的错误窗口。后来才发现&#xff0c;这套工业级视觉开…

作者头像 李华
网站建设 2026/4/16 21:57:28

揭秘AI写教材:高效工具与低查重方法大公开

撰写教材的困境与 AI 的助力 撰写教材的过程中&#xff0c;总是会踩到“慢节奏”的雷区。即使框架和资料早已准备就绪&#xff0c;往往还是在内容的撰写上陷入困境——一句话琢磨半天也觉得表达不够准确&#xff1b;章节之间的衔接&#xff0c;总是想破脑袋也找不到合适的转接…

作者头像 李华
网站建设 2026/4/16 21:55:24

CubeMX+HAL库实战:STM32H743时钟配置常见错误及修复方法

STM32H743时钟配置实战&#xff1a;从CubeMX陷阱到高效调优 在嵌入式开发领域&#xff0c;时钟配置堪称微控制器的"心跳调节器"。作为STMicroelectronics旗舰级产品&#xff0c;STM32H743凭借其400MHz主频和双核架构成为高性能应用的宠儿。但当我第一次使用CubeMX配置…

作者头像 李华