1. 问题现象与排查思路
最近在线上环境遇到一个典型问题:Java应用突然出现内存异常告警,同时伴随CPU使用率飙升到90%以上。这种复合型问题往往让开发者头疼,因为内存和CPU问题经常相互掩盖、互为因果。经过多次实战,我总结出一套行之有效的排查方法。
当看到监控系统同时报出OOM和CPU峰值时,首先要明确三个关键点:
- 内存问题是因还是果?是内存泄漏导致频繁GC引发的CPU飙升,还是高CPU运算导致对象快速堆积?
- 问题是否可稳定复现?突发性问题和渐进式问题的排查策略完全不同
- 影响范围有多大?单实例问题还是集群级问题?
2. 排查工具链准备
工欲善其事必先利其器,线上排查需要提前准备好这些工具:
2.1 基础监控工具
- top -Hp:查看具体线程CPU占用
- vmstat 1:监控系统级内存和CPU变化
- jstat -gcutil:观察GC情况
- jmap -histo:快速查看对象分布
2.2 进阶分析工具
bash复制# 堆转储(需谨慎使用)
jmap -dump:live,format=b,file=heap.hprof <pid>
# 线程转储
jstack -l <pid> > thread.txt
# 连续采样(适合突发问题)
for i in {1..10}; do jstack <pid> >> stacks.txt; sleep 0.5; done
2.3 可视化工具
- Eclipse MAT:内存分析神器
- VisualVM:实时监控利器
- Arthas:阿里开源的诊断工具
重要提示:生产环境使用jmap dump前务必评估影响,大堆内存可能导致服务暂停
3. 内存异常排查实战
3.1 快速定位内存问题
当收到内存告警时,按这个顺序排查:
- 先用
jcmd <pid> GC.heap_info查看堆内存分布 - 执行
jmap -histo:live <pid> | head -20看对象TOP榜 - 如果发现某个类实例数异常,用MAT分析引用链
常见内存问题模式:
- 直方图模式:某个类实例数远超预期
- 支配树模式:少数对象持有大量内存
- OQL查询:定位符合特定条件的对象
3.2 MAT分析技巧
使用Eclipse MAT分析堆转储时,重点关注:
- Leak Suspects报告
- Dominator Tree中的大对象
- 对象间的引用关系图
典型内存泄漏场景:
- 静态集合未清理
- 缓存无限增长
- 未关闭的资源(连接、流等)
4. CPU高负载排查指南
4.1 线程级分析
当CPU飙升时,按这个流程处理:
bash复制# 1. 找出Java进程PID
top
# 2. 查看进程内线程CPU占用
top -Hp <pid>
# 3. 将线程ID转为16进制
printf "%x\n" <tid>
# 4. 在jstack结果中查找对应线程
4.2 常见CPU问题模式
- 死循环:某个线程持续100%占用
- 锁竞争:大量线程处于BLOCKED状态
- GC风暴:多个GC线程高负载
4.3 性能热点定位
使用arthas的profiler命令:
bash复制# 采样CPU
profiler start -d 30 -f /tmp/flamegraph.html
火焰图能直观显示:
- 方法调用栈深度
- 各方法耗时占比
- 阻塞等待情况
5. 复合问题联动分析
当内存和CPU问题同时出现时,要特别注意它们的关联性:
5.1 GC导致的CPU飙升
现象特征:
- GC时间占比高(通过jstat观察)
- Young GC或Full GC频繁
- 内存使用率周期性波动
解决方案:
- 调整堆大小比例(-XX:NewRatio)
- 优化GC策略(如G1调优)
- 检查是否存在内存泄漏
5.2 计算密集导致的内存问题
现象特征:
- 大量临时对象创建
- 方法区或元空间持续增长
- CPU高的线程在做复杂运算
解决方案:
- 优化算法复杂度
- 引入对象池
- 增加缓存层级
6. 线上排查注意事项
-
安全第一原则:
- 避免在高峰期执行高危操作
- dump前评估内存大小
- 采样而非全量抓取
-
信息收集要点:
- 记录操作时间点
- 保存监控系统截图
- 收集完整的日志上下文
-
典型避坑指南:
- 不要直接kill -9
- 避免频繁执行jmap
- 线程转储间隔至少5秒
7. 防御性编程建议
根据实战经验,这些编码习惯能减少问题发生:
- 资源管理:
java复制// 使用try-with-resources确保关闭
try (InputStream is = new FileInputStream(file)) {
// 操作资源
}
- 集合使用:
- 预估集合大小避免扩容
- 使用WeakHashMap做缓存
- 定期清理过期数据
- 性能监控:
- 添加Prometheus指标
- 关键方法添加耗时统计
- 建立健康检查机制
8. 经典案例复盘
最近处理的一个典型case:
- 现象:每天凌晨3点CPU飙升,伴随OOM
- 排查:通过定时采样发现与报表生成任务重合
- 根因:报表生成时加载全量数据到内存
- 解决:改为分页处理+流式计算
关键教训:
- 周期性问题的排查要结合业务场景
- 批量操作必须考虑数据量级
- 内存使用要有上限控制
9. 进阶工具链推荐
对于复杂问题,可以考虑:
- JFR(Java Flight Recorder)
bash复制# 开启记录
jcmd <pid> JFR.start duration=60s filename=recording.jfr
# 分析工具
JDK Mission Control
- Async-profiler
bash复制./profiler.sh -d 30 -e cpu -f profile.html <pid>
- Perf工具
bash复制perf record -F 99 -g -p <pid> -- sleep 30
perf script > out.perf
这些工具可以提供:
- 锁竞争分析
- 内存分配热点
- 系统调用跟踪
10. 总结与个人心得
在多年的线上问题排查中,我深刻体会到:
- 现象背后的关联性比单一指标更重要
- 好的监控系统能节省90%的排查时间
- 保持原始现场(线程栈、堆快照)非常关键
- 建立知识库记录历史问题极有价值
最后分享一个实用技巧:在测试环境定期执行kill -3 <pid>模拟问题场景,锻炼团队的应急能力。真实故障来临时,冷静和熟练度往往比技术更重要。
