学习日报|线程池 OOM
一、问题本质(一句话)
任务到达速率 λ ≫ 服务速率 μ + 队列无界/过大 ⇒ 任务对象堆积 ⇒ OOM。
速判:尖刺=流量突发;缓慢上涨=外部依赖变慢;隐蔽上涨=隐式线程池/内存泄漏。
二、四类高频场景
突发流量 + 配置不合理
core=3、max=5、queue=1000/无界,活动高峰到来,队列瞬间暴涨。消费阻塞(外部依赖变慢)
第三方从 100ms 退化到 10s,工作线程被长期占用,队列“温水煮青蛙”式堆积。任务对象内存泄漏 + 堆积
未关闭的 I/O/进程/缓冲、持久引用导致单位任务占用异常。隐式线程池
@Async、MQ 消费者、定时任务框架自带线程池默认无界或参数不当。
三、解决方案(组合拳)
有界化线程池:Array/LinkedBlockingQueue(N),N≈maxPool×2~5;不同接口分池隔离。
拒绝策略:快速失败 + 日志/打点 + 告警,杜绝“闷声堆积”。
入口限流:网关令牌桶/漏桶;活动期把阈值前置到入口。
削峰/背压:用 MQ 缓冲;控制消费者并发与批量。
超时 / 熔断 / 重试:外部调用必须设置;重试仅对幂等,加退避+抖动。
降级兜底:非关键路径返回骨架数据/缓存快照。
观测与告警:active/queue/rejected、RT(P95/P99)、堆使用率、GC;设阈值报警。
四、容量与参数(速查)
core ≈ CPU核 × (1 + 阻塞系数)
;max = core × 2~4
;keepAlive = 30~120s
。queue ≈ max × 2~5
,结合 Little 定律:N_max = λ × W_max
(最大可接受等待时间)。当
λ > 系统可承载
:限流 + 扩容(多副本/分区)。
五、上线前 Checklist
所有线程池有界、命名规范、按接口隔离。
拒绝策略 = 快速失败 + 打点 + 告警(压测验证触发路径)。
网关限流/MQ 削峰,消费者并发/批量可调。
外部调用超时/熔断/重试(仅幂等)。
看板与报警:线程池 / 业务 / JVM 指标齐全。
演练:活动/故障演练验证拒绝与降级路径。
六、最小可用代码片段(Java)
// 有界线程池 + 自定义拒绝(快速失败)
ThreadPoolExecutor ex = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(2000),(r, e) -> { // 打点/告警在这里throw new RejectedExecutionException("系统繁忙,请稍后再试"); }
);// 任务超时取消(避免消费阻塞)
Future<?> f = ex.submit(task);
try { f.get(3, TimeUnit.SECONDS); }
catch (TimeoutException e) { f.cancel(true); }
七、记忆点(一句话)
有界 + 限流 + 超时熔断 + 背压削峰 + 观测告警 → 把不确定性挡在可控边界内。
——如果你给我你当前线程池参数、峰值流量、可接受等待时间 W_max 和外部依赖 RT,我直接帮你算一版推荐配置(core/max/queue)和网关限流阈值。