现象识别第一步:从监控指标发现异常

当系统中存在周期性Work对象未清理时,最直接的暴露路径来自三个基础监控指标:内存、线程数和连接数。这三个指标在Work泄漏场景下形成“异常三角”——单独看每个指标都可能指向不同原因,但三者同时偏离基线,几乎可以锁定“对象未释放”类问题。本章建立一套从原始监控数据到初步研判的标准流程。

1.1 异常指标三角:三个信号必须同时出现

Work未清理的典型监控画像不是单一指标的异常,而是三个指标在时间轴上同步爬升。下表对比了每个指标的正常模式与泄漏模式:

监控指标 正常模式 Work泄漏模式
堆内存使用率 GC后稳定回落,呈锯齿状,基线不漂移 Full GC后回收量逐周期减少, committed 与 used 同步爬升,无法回到安全水位
活跃线程数 线程池大小稳定,idle 线程占比较高 线程数逐周期增加,且 idle 线程比例下降;jstack 可见大量 Work 相关线程处于 WAITING 或 TIMED_WAITING
DB / 连接池连接数 连接数在池化上限内波动,空闲连接及时回收 活动连接数持续逼近池上限,且应用重启前不会自然回落;连接池日志频繁出现 “waiting for connection”
⚠ 常见误判: 只看到内存增长就认为是“内存泄漏”,忽略线程和连接数同步上升,容易误判为“GC配置不当”或“连接池设置过小”。三个指标必须交叉验证,才能与 Work泄漏 建立关联。

1.2 三指标联动的故障树:从现象到根因的推理路径

下图展示了内存、线程、连接数三个异常节点如何通过Work对象未清理这一根因串联起来。每个指标异常都是“结果”,而非“原因”。

Work未清理三指标异常联动分析 Work未清理三指标异常联动分析 Work对象未清理 内存持续增长 线程数持续增加 连接数不降 系统性能持续劣化 结论:Work泄漏 研判要点:三个指标同步增长 → 且无法通过GC/池化回收 → 高度怀疑Work对象未释放
🔍 判读口诀: “内存涨、线程多、连接不降 —— 三箭齐发,锁定Work泄漏。” 如果只有一或两个指标异常,优先排查对应层的独立问题(如GC参数、线程池饥饿、连接池泄露),不必直接进入Work排查流程。

1.3 监控告警阈值与研判规则

在Prometheus、Zabbix或自建监控平台上,按以下规则配置告警并设置研判优先级。三个指标同时触发时,直接进入Work泄漏排查通道。

指标 建议告警阈值 研判规则 排除条件(非Work泄漏)
堆内存 老年代使用率 > 85% 持续 5 个GC周期 Full GC 后使用率回落 < 10% → 积压泄漏 存在大对象直配、MetaSpace 泄漏、GC 日志显示晋升失败
活跃线程数 超过线程池核心数 2 倍且持续上升 jstack 中 Work 相关线程连续 3 次采样递增 突发流量高峰、死锁、IO 阻塞导致线程堆积
连接数 活跃连接 > 池上限 80% 且持续 10 分钟 应用重启后连接数归零 → 非连接池泄漏 慢查询堆积、网络延迟、DB 端连接超时配置过小

1.4 快速定位命令:采集三指标现场数据

发现告警后,立即在三个维度执行快照命令,保留第一现场。以下命令适用于典型Java应用,其他语言栈可替换对应的工具:

# 内存快照:查看堆使用率与GC历史
jstat -gcutil <PID> 1000 5

# 线程快照:统计线程总数,筛选Work相关线程
jstack <PID> | grep "Work" | wc -l
jstack <PID> | grep -E "pool-|work|task" | head -20

# 连接数快照:查看应用进程持有的连接数(以MySQL为例)
netstat -anp | grep <PID> | grep ESTABLISHED | grep 3306 | wc -l

# 综合采样(每30秒一次,持续3分钟)
for i in 1 2 3 4 5 6; do
  echo "=== Sampling $i ==="
  jstat -gcutil <PID> | tail -1
  jstack <PID> | grep "Work" | wc -l
  netstat -anp | grep <PID> | grep ESTABLISHED | grep 3306 | wc -l
  sleep 30
done
📌 现场保留原则: 不要重启应用!不要执行Full GC!先采集至少3组快照(间隔30秒),确认三个指标是否同步上升。重启或强制GC会破坏现场,导致泄漏根因无法追溯。

1.5 常见误判场景:这些“假阳性”必须排除

即使三个指标同时异常,仍有几种情况与Work泄漏无关。下表列出三个典型误判及快速验证方法:

误判场景 表现 快速验证方法
流量突增 三指标同步涨,但请求量也同步增长 对比QPS/RPS曲线:若与流量正相关且流量回落后指标回复,不是泄漏
连接池泄漏 连接数持续上升,内存和线程数相对稳定 检查连接池监控:getNumActive 持续增长且 getNumIdle 为零,独立修复连接池即可
线程池饥饿+重试风暴 线程数急速上涨,连接数和内存缓慢跟进 jstack 查看线程栈:大量线程阻塞在同一个锁或IO点,而非 Work 任务堆积

1.6 实战排查清单:现象识别阶段必须完成的动作

以下清单帮助团队在告警触发后的15分钟内完成初步研判,输出是否进入Work泄漏排查通道:

  1. 确认三指标同步性 — 调出最近30分钟的监控曲线,验证内存、线程、连接数是否呈同步爬升趋势。
  2. 排除流量干扰 — 对比同一时段的QPS/TP99曲线,确认指标增长与流量增长不成比例。
  3. 执行三次快照 — 按1.4节命令采集3组数据,计算每个指标的增量斜率。
  4. 标记Work相关对象 — 用 jstack 或等价工具统计包含 “Work”、“Task”、“Job” 等关键字的线程数量。
  5. 检查GC回收效果 — 通过 jstat 观察Full GC后老年代回收比例:若回收量逐次递减,则判定为积压式泄漏。
  6. 输出研判结论 — 填写下方决策记录,供下一章(数据采集)使用。
📋 决策记录模板(复制到工单):
【监控时段】____ 至 ____
【内存趋势】上升斜率 ____ MB/min  |  【线程趋势】上升斜率 ____ 线程/min  |  【连接趋势】上升斜率 ____ 连接/min
【是否三指标同步】是 / 否  |  【是否排除流量干扰】是 / 否
【初步研判】☐ 进入Work泄漏排查通道  |  ☐ 转其他排查路径
【负责人】____  【时间】____

完成上述清单后,如果三项指标均符合“同步增长 + 无法自然回收”的特征,即可确认进入下一阶段——数据采集,通过堆转储、线程DUMP和连接池监控日志锁定具体的Work对象类型与引用链。