Java 运维诊断指南:线上问题处理操作指南

总结摘要
Java 运维诊断指南:线上问题处理操作指南

Java 线上问题处理操作指南

一、整体排查大纲

1. 总:故障定性与目标

  • 明确故障现象:CPU飙升 + 内存飙升 + 服务不可用(Java实例,Spring微服务)
  • 确定目标优先级:止血(恢复业务)→ 保留现场 → 根因定位 → 永久修复
  • 建立问题分类框架(参考文档中的“排查方向”):
    • 应用程序本身(代码缺陷、JVM参数、资源使用)
    • 上下游系统(流量突增、依赖故障)
    • 机器/容器环境(资源争抢、网络、入侵)
    • 第三方调用(作为被调方流量突增,或作为调方依赖雪崩)

2. 分:按阶段逐步深入

阶段一:快速止血(恢复业务)

  • 决策:摘流 or 重启?保留现场 or 立即恢复?
  • 操作:摘除异常实例,确认业务恢复

阶段二:现场数据收集(证据链构建)

  • 需要什么信息?为什么?
  • 权衡:哪些数据必须取,哪些可能加重故障

阶段三:假设驱动分析(定位根因)

  • 基于文档的常见故障类型,建立假设清单
  • 用多维度数据交叉验证,逐步缩小范围

阶段四:解决与验证

  • 临时措施(重启、调参)与永久修复(代码、配置)
  • 验证问题解决,监控恢复

阶段五:复盘与优化

  • 避免同类问题,提升系统韧性

3. 总:总结核心原则

  • 证据链优于直觉
  • 止血优先于分析
  • 多维度交叉验证
  • 工具是手段,思路是核心

二、详细排查思路与思考过程

阶段一:快速止血(恢复业务)

思考

  • 报警级别“高级”,且明确“无法提供服务”,说明该实例已无法处理请求。此时必须立即恢复业务,不能因为追求现场而让故障持续扩大。
  • 微服务多实例多机房架构的优势在于可以快速切流。

需要的信息

  • 注册中心/负载均衡中该实例的状态(是否已被自动剔除)
  • 其他实例的负载情况,确认切流后业务是否正常

操作

  1. 若实例未被自动摘除,立即在运维平台将该实例从注册中心或负载均衡中下线。
  2. 观察业务指标(如接口成功率、响应时间)是否恢复。
  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
    • 检查是否频繁分配直接内存未释放。

假设 7:机器/容器层面问题

  • 触发证据:CPU sys 占比高,或内存被其他进程占用,或网络丢包。
  • 验证方法
    • 查看宿主机监控(若平台支持)。
    • 通过终端执行 topfreenetstat 等命令检查。

假设 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、重启的标准流程,定期演练。
  • 容量规划:根据业务增长预估流量,提前扩容。

三、总结:核心原则与评估优化

核心原则

  1. 止血优先,现场其次:恢复业务是第一要务,但尽量保留现场;当二者冲突时,业务优先。
  2. 证据驱动,避免臆测:每个决策都要有数据支撑,用证据链验证假设。
  3. 多维度交叉验证:单一指标容易误判,结合 CPU、内存、GC、线程、日志、依赖一起看。
  4. 工具是手段,思路是核心:即使平台功能强大,也要清楚每一步的目的,带着思考去操作。

END