Java 运维诊断指南:线上问题处理操作指南
总结摘要
Java 运维诊断指南:线上问题处理操作指南
Java 线上问题处理操作指南
一、整体排查大纲
1. 总:故障定性与目标
- 明确故障现象:CPU飙升 + 内存飙升 + 服务不可用(Java实例,Spring微服务)
- 确定目标优先级:止血(恢复业务)→ 保留现场 → 根因定位 → 永久修复
- 建立问题分类框架(参考文档中的“排查方向”):
- 应用程序本身(代码缺陷、JVM参数、资源使用)
- 上下游系统(流量突增、依赖故障)
- 机器/容器环境(资源争抢、网络、入侵)
- 第三方调用(作为被调方流量突增,或作为调方依赖雪崩)
2. 分:按阶段逐步深入
阶段一:快速止血(恢复业务)
- 决策:摘流 or 重启?保留现场 or 立即恢复?
- 操作:摘除异常实例,确认业务恢复
阶段二:现场数据收集(证据链构建)
- 需要什么信息?为什么?
- 权衡:哪些数据必须取,哪些可能加重故障
阶段三:假设驱动分析(定位根因)
- 基于文档的常见故障类型,建立假设清单
- 用多维度数据交叉验证,逐步缩小范围
阶段四:解决与验证
- 临时措施(重启、调参)与永久修复(代码、配置)
- 验证问题解决,监控恢复
阶段五:复盘与优化
- 避免同类问题,提升系统韧性
3. 总:总结核心原则
- 证据链优于直觉
- 止血优先于分析
- 多维度交叉验证
- 工具是手段,思路是核心
二、详细排查思路与思考过程
阶段一:快速止血(恢复业务)
思考:
- 报警级别“高级”,且明确“无法提供服务”,说明该实例已无法处理请求。此时必须立即恢复业务,不能因为追求现场而让故障持续扩大。
- 微服务多实例多机房架构的优势在于可以快速切流。
需要的信息:
- 注册中心/负载均衡中该实例的状态(是否已被自动剔除)
- 其他实例的负载情况,确认切流后业务是否正常
操作:
- 若实例未被自动摘除,立即在运维平台将该实例从注册中心或负载均衡中下线。
- 观察业务指标(如接口成功率、响应时间)是否恢复。
- 关键权衡:摘流后,该实例仍在运行,可能继续消耗资源,但已不影响用户。此时应保留现场,不要急于重启,以便收集诊断数据。
如果摘流后实例仍在恶化(如即将 OOM 或影响宿主机):
- 立即重启,牺牲现场保稳定。但重启前尽量快速抓取 thread dump 和关键日志。
阶段二:现场数据收集(证据链构建)
思考:
- 我需要用数据来验证假设,而不是凭感觉猜测。平台提供了丰富的监控和诊断工具,但不同数据获取成本和风险不同。
- 优先获取轻量级、无风险的数据,再根据情况决定是否做重量级操作(如 heap dump)。
所需数据清单(按优先级排序):
| 数据项 | 获取方式 | 目的 | 风险/成本 |
|---|---|---|---|
| 系统指标(CPU、内存、网络、磁盘) | 实例监控 | 判断资源使用模式 | 无,立即获取 |
| GC 指标(GC 次数、耗时、老年代占用) | 实例监控 | 判断是否内存压力导致 CPU 高 | 无,立即获取 |
| 线程快照(thread dump) | 应用诊断 → 导出线程快照 | 分析线程状态(阻塞、等待、死锁、热点) | 轻量,必须做 |
| 火焰图(CPU 热点) | 应用诊断 → 生成火焰图 | 定位 CPU 热点方法 | 轻量,优先做 |
| 日志(错误日志、GC 日志) | 日志分析 → 关键字检索 | 直接发现异常信息(OOM、死锁、超时) | 无,立即检索 |
| 依赖服务监控(下游 RPC、DB、MQ) | 服务监控 | 判断是否外部依赖阻塞或故障 | 无,立即查看 |
| 堆内存快照(heap dump) | 应用诊断 → 导出堆快照 | 分析内存泄漏、大对象 | 较重,可能触发 Full GC 或 OOM,需权衡 |
| JVM 参数、环境变量 | 目录文件查看 | 确认配置是否合理 | 无,可随时查看 |
| 直接内存信息 | 应用诊断(若支持 NMT) | 排查直接内存泄漏 | 轻量,可选 |
关键权衡:
- 堆快照做不做?
如果老年代已占用 80% 以上且持续增长,dump 可能引发 Full GC 甚至 OOM,导致容器被 kill。若已摘流,且内存压力尚可接受,可以尝试;若风险高,先依靠其他数据定位,必要时再 dump。 - 两次 thread dump 间隔多久?
间隔 10-15 秒,对比线程状态变化,可判断线程是否在持续堆积(如不断创建新线程等待)。 - 火焰图与 thread dump 哪个先?
CPU 高场景,火焰图能直接显示热点函数,优先做。若火焰图热点在 GC 线程,则转向内存分析;若在业务代码,则结合 thread dump 查看具体线程栈。
阶段三:假设驱动分析(定位根因)
思考:
- 我不直接下结论,而是根据已有数据建立多个合理假设,然后逐一验证,用证据链排除或确认。
- 参考文档中的常见故障类型(内存泄漏/溢出、死锁、线程阻塞、CPU 飙升等)和排查方向(应用本身、上下游、机器、第三方),构建假设清单。
假设清单(按可能性排序,结合数据动态调整):
假设 1:内存泄漏 / 堆太小 → 频繁 GC 导致 CPU 高
- 触发证据:老年代持续增长,GC 次数多,Full GC 频繁,GC 线程占 CPU 高。
- 验证方法:
- 查看 GC 监控,若老年代占用 > 80% 且每次 GC 后下降不明显 → 可能泄漏。
- 若频繁 Full GC 且堆未达到上限 → 可能堆太小。
- 补充:导出堆 dump,分析大对象(如 HashMap 过大、缓存未清理、ThreadLocal 未释放)。
假设 2:业务死循环 / 高 CPU 计算
- 触发证据:CPU 持续飙高(如 100%),但内存稳定,GC 正常。
- 验证方法:
- 火焰图定位到具体方法(如某业务循环)。
- thread dump 看到大量 RUNNABLE 线程在同一方法栈。
- 结合日志,看是否有异常的业务数据导致无限循环。
假设 3:外部依赖阻塞(下游 RPC / DB 慢或不可用)
- 触发证据:线程数异常高,大量线程处于 WAITING/TIMED_WAITING,等待数据库连接或 RPC 响应。
- 验证方法:
- thread dump 查看等待栈,确认是否在获取连接、执行 SQL、调用 RPC。
- 依赖监控显示下游响应时间飙升、错误率上升、连接池满。
- 日志中出现连接池耗尽、超时等错误。
假设 4:线程池配置不当 / 流量突增
- 触发证据:QPS 突增(对比历史基线),线程池队列堆积,线程数暴增。
- 验证方法:
- 服务监控查看入口流量。
- thread dump 中看到大量线程在等待队列任务。
- 日志中出现
RejectedExecutionException。
假设 5:死锁
- 触发证据:部分线程完全无法执行,系统响应停滞。
- 验证方法:
- thread dump 中看到 “Found one Java-level deadlock” 字样。
- 分析锁持有关系,定位代码。
假设 6:直接内存泄漏
- 触发证据:系统内存高,但堆内存正常,且应用使用 Netty、DirectByteBuffer 等。
- 验证方法:
- 查看平台是否支持 NMT(Native Memory Tracking),或通过 OGNL 查询
BufferPoolMXBean。 - 检查是否频繁分配直接内存未释放。
- 查看平台是否支持 NMT(Native Memory Tracking),或通过 OGNL 查询
假设 7:机器/容器层面问题
- 触发证据:CPU sys 占比高,或内存被其他进程占用,或网络丢包。
- 验证方法:
- 查看宿主机监控(若平台支持)。
- 通过终端执行
top、free、netstat等命令检查。
假设 8:第三方流量突增
- 触发证据:作为被调用方,入口流量突增超出处理能力。
- 验证方法:
- 查看 HTTP/RPC 入口监控,对比历史基线。
- 查看上游服务的监控,确认是否有流量异常。
如何快速过滤:
- 先看 GC 监控:若 GC 频繁,优先假设 1。
- 再看火焰图:若热点在业务代码,优先假设 2;若热点在 GC 线程,回到假设 1。
- 再看 thread dump:若大量 WAITING,优先假设 3。
- 结合依赖监控和日志,快速验证外部依赖。
阶段四:解决与验证
根据定位的根因,采取相应措施:
| 根因 | 临时措施 | 永久措施 |
|---|---|---|
| 内存泄漏 | 重启实例,临时增大堆内存 | 修复代码(如缓存清理、避免对象积累) |
| 堆太小 | 调整 -Xmx 参数,重启 | 根据业务评估设置合理值,并配置监控告警 |
| 死循环 | 重启,热修复(若支持) | 修复代码逻辑,增加边界检查 |
| 外部依赖阻塞 | 重启,临时增加超时/熔断 | 优化依赖性能,配置熔断降级 |
| 流量突增 | 扩容,限流 | 配置弹性伸缩,限流阈值 |
| 死锁 | 重启 | 修复锁顺序,使用并发工具类 |
| 直接内存泄漏 | 重启,增加 -XX:MaxDirectMemorySize | 修复代码,监控直接内存 |
| 机器/容器问题 | 迁移实例,排查宿主机 | 隔离问题节点,加强监控 |
验证:
- 重启或调整后,监控指标(CPU、内存、GC、线程数)恢复基线。
- 业务接口成功率、响应时间恢复正常。
- 观察一段时间,确认无复发。
阶段五:复盘与优化
思考:
- 问题解决了,但如何避免再次发生?如何让系统更健壮?
优化方向:
- 监控增强:增加关键指标告警(如 GC 时间 > 1s、线程数 > 阈值、连接池使用率 > 80%)。
- 限流降级:在网关或应用层增加限流,对下游依赖配置熔断、超时、重试策略。
- JVM 参数调优:根据业务特点选择合适的 GC(如 G1),设置合理的元空间、直接内存上限,开启 GC 日志。
- 代码层面:优化线程池配置、连接池参数、缓存策略,避免内存泄漏模式。
- 应急预案:编写故障手册,明确摘流、dump、重启的标准流程,定期演练。
- 容量规划:根据业务增长预估流量,提前扩容。
三、总结:核心原则与评估优化
核心原则
- 止血优先,现场其次:恢复业务是第一要务,但尽量保留现场;当二者冲突时,业务优先。
- 证据驱动,避免臆测:每个决策都要有数据支撑,用证据链验证假设。
- 多维度交叉验证:单一指标容易误判,结合 CPU、内存、GC、线程、日志、依赖一起看。
- 工具是手段,思路是核心:即使平台功能强大,也要清楚每一步的目的,带着思考去操作。