
Java ThreadLocal 内存泄漏线程池场景下的成因与正确用法ThreadLocal 的 key 是弱引用、value 是强引用配合线程池的线程复用一旦忘记remove()就会造成内存泄漏。本文讲清楚泄漏的底层原理给出 try-finally remove 的正确姿势和排查方法。一、问题案例线程池里用 ThreadLocal 存用户上下文忘了清理。跑一段时间后内存持续上涨最终 OOM。privatestaticfinalThreadLocalUserContextCONTEXTnewThreadLocal();publicvoidhandle(Useruser){CONTEXT.set(newUserContext(user));// ... 业务逻辑 ...// ❌ 忘了 CONTEXT.remove()}二、原理详解key 弱引用、value 强引用ThreadLocal 底层是每个线程Thread内部的一个ThreadLocalMap它以 ThreadLocal 为 key、以用户数据为 value。关键点key 是弱引用WeakReference 指向 ThreadLocal 对象value 是强引用指向真正的用户数据。线程池的线程是复用的线程不销毁 → 线程内部的 ThreadLocalMap 不销毁 → 里面的 value 一直被强引用 → GC 无法回收。而 key 的弱引用只在ThreadLocal 对象本身被 GC时才会失效value 不会自动清理。于是每来一个任务、set 一次就漏一个 value内存越涨越高。三、实战代码正确姿势用完必须remove()并且习惯放在finally里try{CONTEXT.set(newUserContext(user));// ... 业务逻辑 ...}finally{CONTEXT.remove();// ✅ 用完就清异常也能清}四、常见踩坑remove 要放 finally如果业务抛异常没 finally 的 remove 就不会执行照样漏。框架帮你清手动用要自己清Spring 的RequestContextHolder底层也是 ThreadLocal但容器会清理自己手动 new 的 ThreadLocal 得自己负责。InheritableThreadLocal 同样要 remove它能传给子线程但泄漏特性一样。五、总结线程池 ThreadLocal 必须成对 removeremove 放 finally。value 是强引用线程不死就不会被回收这是泄漏的根因。遇到「内存慢涨 老年代满 线程池场景」先怀疑 ThreadLocal 泄漏。我是无羡小剑全栈偏后端的独立开发者。作品集无羡 · 独立开发者作品集如果对你有帮助欢迎点赞、收藏、关注。