oom隐蔽诱因是拒绝策略“假装能接”却无容量控制:abortpolicy配无界队列或自定义策略忽略offer返回值,导致任务失控堆积;需检查队列容量、堆内存中队列retained heap占比及拒绝日志是否真实触发。
直接看拒绝策略是否在“假装能接”,而实际把任务塞进队列却不控制容量——这是oom最隐蔽的诱因之一。
确认拒绝策略是否形同虚设
很多团队误以为设置了拒绝策略就万事大吉,但若用的是 AbortPolicy + 无界队列,或自定义策略里写了 queue.offer(task) 却没做容量判断,那就等于给内存开了个后门。
- 检查代码中是否出现类似
if (workQueue.offer(runnable)) { ... }而未配合!workQueue.offer()后的兜底处理 - 重点排查自定义
RejectedExecutionHandler实现:是否绕过线程池原生逻辑,私自往队列 add/offer 且不校验返回值 - 用
executor.getQueue().remainingCapacity()在运行时采样,若长期为Integer.MAX_VALUE或接近 0,说明策略已失效或队列失控
抓取堆内存中真实的堆积证据
OOM 发生后,别只看异常日志。用 MAT 或 JProfiler 打开 heap dump,直奔两个关键对象:
- LinkedBlockingQueue / ArrayBlockingQueue 的 elementData 数组:看 retained heap 是否占满堆的 60% 以上;若单个队列 Retained Heap > 1GB,基本锁定是任务堆积
-
队列中每个 Runnable/Callable 实例引用的对象图:比如
ImportTask持有User(6KB),50 万个就吃掉 3GB —— 这不是泄漏,是“合法但失控”的堆积 - 搜索
java.util.concurrent.ThreadPoolExecutor$Worker,对比activeCount和queue.size():若前者只有 10,后者高达 20 万,说明策略完全没起作用
验证策略是否真正触发并生效
拒绝策略不是“设了就跑”,必须确认它在压力下真实执行:
- 在自定义
RejectedExecutionHandler中加明确日志:log.warn("Task rejected: {}, queueSize={}", task, executor.getQueue().size()) - 压测时观察该日志是否出现 —— 如果一直没打,说明任务根本没走到拒绝阶段,而是被无界队列默默吞了
- 用
jstack查看线程状态:若大量线程卡在LinkedBlockingQueue.offer的await()(尤其用SynchronousQueue时),说明生产者线程正在死等消费者,策略压根没机会介入
修复方向要堵住“假接收”漏洞
真正的安全策略必须做到“收得进、控得住、退得出”:
- 禁用
Executors.newFixedThreadPool和newCachedThreadPool—— 它们默认使用无界队列或无上限线程数 - 显式构造
ThreadPoolExecutor,队列必须指定容量:new LinkedBlockingQueue(200) - 拒绝策略优先选
CallerRunsPolicy:它不隐藏压力,而是让调用线程自己执行,天然限流 - 若必须用自定义策略,务必检查
queue.offer()返回值,失败时明确丢弃、告警或降级,绝不静默吞任务











