线程本身不直接泄漏内存,但不当使用会引发内存泄漏:线程池配置失当导致任务堆积与对象驻留;ThreadLocal未remove造成强引用滞留;虚拟线程阻塞引发堆外内存增长;非静态内部类等隐式持有外部对象延长引用链。

线程本身不直接“泄漏内存”,但线程的不当使用会成为内存泄漏的关键诱因——尤其是当线程长期持有对象引用、未释放资源或生命周期失控时,对象无法被 GC 回收,内存便持续堆积。
线程池配置失当引发的隐性泄漏
线程池若未合理关闭或参数设置不合理,容易导致任务堆积、线程滞留、对象长期驻留堆中。例如:核心线程数设为 0 且 keep-alive 时间过长,空闲线程迟迟不销毁;或使用无界队列(如 LinkedBlockingQueue 无容量限制),任务不断积压,关联的 Runnable 对象及其闭包引用无法释放。
- 务必在应用关闭时显式调用 executor.shutdown() 或 shutdownNow(),并等待终止完成
- 避免使用无界队列,优先选用有界队列(如 ArrayBlockingQueue),配合拒绝策略(如 AbortPolicy 或 DiscardPolicy)控制风险
- 根据业务吞吐量和系统资源设定合理的 corePoolSize / maxPoolSize / keepAliveTime,避免线程过度驻留
ThreadLocal 未清理造成的强引用滞留
ThreadLocal 是高频泄漏点,尤其在线程复用场景(如 Tomcat 线程池、自定义线程池)中。若 set 后未调用 remove(),其内部 ThreadLocalMap 中的 Entry 会一直持有 value 引用,而该 value 又可能关联大量对象(如上下文、缓存、大数组),导致整个对象图无法回收。
- 每次使用 ThreadLocal 后,在 finally 块中执行 threadLocal.remove()
- 避免在 ThreadLocal 中存储大型对象或复杂对象图;必要时改用弱引用包装(WeakReference<T>)
- 排查时可检查堆转储中是否存在大量 java.lang.ThreadLocal$ThreadLocalMap$Entry,并追踪其 value 的保留路径
虚拟线程带来的堆外内存风险
Java 21+ 的虚拟线程虽轻量,但其底层仍依赖堆外内存(如栈空间、调度元数据)。若大量虚拟线程执行阻塞 I/O 或长时间挂起(如未设超时的 sleep、网络调用),JVM 可能无法及时回收相关资源,造成堆外内存缓慢增长,最终触发 OOM: Direct buffer memory。
- 对所有阻塞操作(如 SocketChannel.read、Files.lines)强制添加超时机制
- 启用 JFR 监控虚拟线程生命周期:jcmd <pid> JFR.start name=VTLeak settings=profile duration=60s
- 避免在虚拟线程中持有大对象引用;优先使用结构化并发(StructuredTaskScope)管理作用域生命周期
线程持有外部对象导致的引用链延长
非静态内部类、匿名 Runnable/Callable、Lambda 表达式等,常隐式捕获外围实例(this),使本应短命的对象被迫与线程生命周期绑定。若线程长期运行(如守护线程、定时任务线程),外围对象将永远无法回收。
- 优先使用静态内部类 + 显式传参替代非静态内部类;Lambda 中避免直接引用 this 或成员变量
- 检查线程堆栈中是否频繁出现类似 OuterClass$1.run 的调用链,结合 MAT 分析其引用链
- 对定时任务(Timer、ScheduledExecutorService)确保任务对象不持有业务上下文;必要时用 WeakReference 包装回调对象

















