是LinkedBlockingQueue无界堆积引发的问题。通过jmap查Node实例数量、MAT分析heap dump确认队列占用高,jstack发现大量线程阻塞在take(),结合线程池使用无界队列且缺乏合理拒绝策略即可定位。
直接看堆内存和线程状态,就能快速定位是否是 linkedblockingqueue 无界堆积引发的问题。
查堆内存:确认队列节点是否占满堆
用 jmap -histo <pid> | grep LinkedBlockingQueue\$Node 查看队列节点实例数量。如果数量持续增长(比如几万甚至上百万),且占总对象数比例极高,基本就是任务在队列里越积越多。配合 MAT 打开 heap dump,看 “Leak Suspects Report”,常会直接标出 LinkedBlockingQueue$Node 占用 40% 以上堆内存,并指向某个线程池的 workQueue 字段。
看线程栈:识别“卡住”的消费端
执行 jstack -l <pid> > threaddump.log,搜索关键词:
- LinkedBlockingQueue.take 或 poll
- waiting on condition + AbstractQueuedSynchronizer$ConditionObject
若大量线程停在 take()(如 Tomcat 的 http-nio-exec 线程、自定义线程池的 Worker 线程),说明它们正空等任务——但队列其实已满或消费能力严重不足,任务进得多、出得少,队列持续膨胀。
核对线程池配置:找默认无界队列
检查代码中所有 ThreadPoolExecutor 构造位置,重点关注:
- 是否用了 new LinkedBlockingQueue()(无参构造 → 容量为 Integer.MAX_VALUE)
- 是否没配拒绝策略,或用了 AbortPolicy(丢弃+抛异常)但上游未降级
- 是否核心线程数过小、最大线程数受限,而队列又不限制,导致所有请求全塞进队列
典型危险写法:new ThreadPoolExecutor(2, 4, 60, SECONDS, new LinkedBlockingQueue())
验证与收口:加监控+改配置
临时加 JVM 参数输出 GC 日志,观察老年代是否缓慢但持续上涨;同时在关键线程池上暴露 getQueue().size() 指标到 Prometheus。
修复动作明确两步:
- 把 LinkedBlockingQueue 改为有界:如 new LinkedBlockingQueue(200)
- 拒绝策略选 CallerRunsPolicy(调用线程自己执行,自然限流)或 DiscardOldestPolicy(丢老任务保新)

















