fixedthreadpool 使用无界队列会引发内存溢出,因其不限制任务堆积,导致大量 runnable 对象长期驻留堆中,消费慢于提交时迅速耗尽内存。

FixedThreadPool 用无界队列(LinkedBlockingQueue,容量为 Integer.MAX_VALUE)不是“能装很多”的优点,而是埋下内存失控的引信——任务持续堆积、对象长期驻留堆中,最终触发 OutOfMemoryError。
为什么无界队列会吃光内存
FixedThreadPool 的设计逻辑是:只要活跃线程数未达核心线程数,就新建线程;一旦达到,后续任务全进队列等待。而这个“等待”没有上限:
- 队列本身不拒绝任务,也不限大小,只管往里塞
Runnable或FutureTask - 每个任务对象(尤其带业务数据、IO上下文、大字符串等)都持有堆内存引用
- 线程处理慢(比如远程调用超时、数据库锁表、GC停顿),消费速度远低于提交速度 → 队列长度指数级增长
- 实测案例中,50万个任务 × 平均6KB/任务 = 直接占掉3.2GB堆空间,MAT分析一眼锁定
LinkedBlockingQueue.elementData
典型翻车场景长什么样
不是“CPU打满”或“线程爆满”,而是安静地崩坏:
- Pod 内存从2G缓慢爬升到7G+,但 CPU 使用率始终低于30%
- Tomcat 线程池接近上限,但 FixedThreadPool 自身线程数稳定在 corePoolSize(比如10个),看似“很闲”
- 接口 RT 突然飙升,监控显示自定义队列长度指标峰值破10万+
- 日志里频繁出现
Full GC (Ergonomics),每次回收后堆内存几乎没降(因为队列里的任务还活着)
怎么避免被无界队列反杀
关键不是禁用 FixedThreadPool,而是绕过 Executors 工厂方法,手动构造可控的 ThreadPoolExecutor:
- 用
ArrayBlockingQueue替代LinkedBlockingQueue,显式设容量(如 200) - 搭配拒绝策略:
CallerRunsPolicy(让调用方执行,自然限流)、AbortPolicy(快速失败)、或自定义记录+告警逻辑 - 核心线程数别拍脑袋定:IO 密集型建议 ≈ CPU 核数 × (1 + 平均等待时间 / 平均计算时间),并配合压测验证
- 务必暴露队列长度、活跃线程数、已提交/已完成任务数等指标,接入 Prometheus + Grafana 实时盯梢
顺手踩过的连带坑
你以为换了有界队列就安全了?这些常一起出现:
-
CompletableFuture.supplyAsync()没传自定义线程池 → 默认走ForkJoinPool.commonPool,被一个慢 IO 拖垮整个公共池 - 任务内部用了
ThreadLocal却没清理 → 线程复用导致内存泄漏,加剧 OOM - 拒绝策略选了
AbortPolicy却没做上层兜底 → 接口直接 500,用户体验断崖下跌










