oom定位需同步捕获线程池四类关键变量:activecount、queue.size、未完成任务差值、shutdown状态,并与堆dump按时间戳对齐分析。

发生 OOM 时,光有堆 dump 不够——线程池是否积压、活跃线程数是否异常、队列里堆了多少任务,这些并发状态才是定位“为什么内存涨不下去”的关键。真正的现场快照,得把线程池运行时的几个核心变量在 OOM 触发瞬间一并捕获下来,和堆 dump 对齐时间戳,才能还原真相。
抓取哪些线程池变量才算有效快照
不是所有字段都有价值。重点关注以下四类可直接反映积压与失控状态的运行时值:
- executor.getActiveCount():当前正在执行任务的线程数。长期远低于 corePoolSize,说明任务根本没被消费,大概率卡在队列里
- executor.getQueue().size():等待执行的任务数量。>200 就该预警,>1000 基本确认严重积压
- executor.getCompletedTaskCount() 与 executor.getTaskCount() 的差值:反映未完成任务总量,比队列 size 更全面(含已提交但未入队的)
- executor.isTerminated() / isShutdown():判断线程池是否已被误关闭,导致新任务全被拒绝或阻塞
在 OOM 发生时自动触发快照采集
不能靠人工登录机器查,必须嵌入 JVM 的 OOM 钩子中。推荐用 -XX:OnOutOfMemoryError 调用脚本,脚本内做两件事:保存线程池变量 + 生成线程堆栈。
- Java 端提供一个静态方法暴露关键指标,例如:
ThreadPoolSnapshot.capture("my-executor"),内部用System.currentTimeMillis()打上毫秒级时间戳,并写入带时间前缀的文本文件(如snapshot-1747039080123.txt) - 脚本中同步执行
jstack $PID > thread-$(date +%s%3N).log,确保和变量快照时间接近(误差控制在 100ms 内) - 脚本开头加
sleep 0.1,避免 jstack 在 JVM 崩溃过程中抢资源失败 - 所有快照文件统一存到
/data/dumps/oom-snapshots/,并设置磁盘配额防占满
和堆 dump 关联分析的实操要点
单看快照是孤立的,要和 heap.hprof 对齐才有力:
- OOM 日志里会打印类似
java.lang.OutOfMemoryError: Java heap space at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:420),说明问题出在队列 offer 阶段 → 此时getQueue().size()必然极高 - 用 MAT 打开 dump,按
java.lang.Runnable或业务任务类(如ImportUserTask)排序,看实例数;再对照快照里的getQueue().size(),若两者接近,基本锁定是任务对象本身大且积压 - 检查快照中
getActiveCount()是否为 0 或极低,再结合 jstack 查看线程是否全在WAITING on java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject—— 这是典型的消费者线程全部阻塞,生产者还在狂塞
避免快照本身成为负担的细节
快照逻辑必须轻量、幂等、失败可容忍:
- 不要在快照里做 JSON 序列化或远程上报,只写纯文本。一行一个 key=value,例如:
queue.size=12847 - 捕获逻辑用 try-catch 包裹,任何异常都只打本地 warn 日志,绝不抛出,否则可能干扰 OOM 后的退出流程
- 如果应用使用多个线程池,快照方法应支持传入 ExecutorService 名称,避免所有池共用一个文件造成覆盖
- 容器环境下注意:jstack 需在容器内执行,
-XX:OnOutOfMemoryError脚本也要挂载进容器,且赋予proc:rw权限










