Java 面试题

ThreadLocal原理与内存泄漏:为什么必须remove

"ThreadLocal原理是什么?为什么会内存泄漏?"

为什么面试官会问这道题

ThreadLocal是Java并发经典题,内存泄漏这个角度专门考你有没有真的排查过线上堆内存。Tomcat、Dubbo、Spring Web这些线程池框架最容易把ThreadLocal误用变成生产事故,所以这题非常实战。

如何回答

  1. 1

    讲存储模型:每个Thread对象里有个私有字段threadLocals,类型ThreadLocalMap。key是ThreadLocal实例本身,value是你set的值。数据其实存在Thread上,不在ThreadLocal里。

  2. 2

    讲key的弱引用:ThreadLocalMap的Entry继承WeakReference<ThreadLocal>,代码里最后一个ThreadLocal强引用断掉后,GC回收ThreadLocal,map里的key变null,但value还是强引用。

  3. 3

    讲泄漏链路:Tomcat/Dubbo这类线程池里线程长期存活。key没了但value还在,每个请求都往里加一份垃圾,堆一点点涨直到OOM。

  4. 4

    讲部分清理机制:ThreadLocal的get/set在探测到key为null的slot时会顺手清一下。但这是概率性的,不能依赖。

  5. 5

    正确姿势:try { set(x); ... } finally { remove(); }。Spring的TransactionSynchronizationManager、SecurityContextHolder、RequestContextHolder内部都是这个模式。

参考回答示例

ThreadLocal让每个线程有自己的数据副本,完全不用同步。反直觉的是数据存在哪儿——不在ThreadLocal实例里,而在每个Thread对象的私有字段里。每个Thread都有个threadLocals字段,类型是ThreadLocalMap。你调threadLocal.set(value)时,实际是拿当前线程的map,往里存一个Entry:key是ThreadLocal实例本身,value是你传的值。两个线程对同一个ThreadLocal调get()各读各的map,完全无竞争。内存泄漏的根源在Entry的设计。Entry extends WeakReference<ThreadLocal>,key是弱引用持有。代码里持有ThreadLocal的强引用一旦断掉(比如是个局部变量、或者class被卸载),GC就回收ThreadLocal对象,map里那个Entry的key字段变成null。但value是Entry的普通强字段,继续活着——又因为Thread还活着(线程池场景下线程整个JVM周期都不死),map和这些僵尸Entry就一直堆着。在Tomcat或Dubbo这种有几百个长期工作线程的服务里,每个请求set了ThreadLocal但忘了remove,就往map里留一份value。时间一长就是经典的慢泄漏——堆慢慢涨,最终OOM。ThreadLocal的set/get内部有机会清理null key的Entry——哈希探测时遇到null key的slot会顺手清掉。但这是概率性的,探测不到就不会清。正确姿势必须是:try { threadLocal.set(x); ... } finally { threadLocal.remove(); }——finally里一定要remove。你每天在用的框架都遵循这个模式:Spring的事务同步管理器、Security的SecurityContextHolder、MVC的RequestContextHolder全都在filter或interceptor的afterCompletion里做remove。

实用技巧

  • 讲的时候一定要画清楚:Thread → ThreadLocalMap → Entry[key=弱引用ThreadLocal, value=强引用]。

  • 强调线程池复用——这才是把理论泄漏变成生产事故的关键。

  • finally { remove(); }是必须点到的要点。

  • 临场讲弱引用vs强引用容易绕,即答侠可以帮你快速理清Entry的结构。

常见问题

为什么不把value也做成弱引用?

value是你真正要用的东西,一弱引用GC就回收了,get永远返回null,ThreadLocal就废了。

InheritableThreadLocal有同样问题吗?

有,而且会复制到子线程,泄漏会扩散。线程池场景用阿里的TransmittableThreadLocal更安全。

ThreadLocal是线程安全的吗?

设计上天然线程安全——每个线程看自己的value不用同步。但value对象本身如果被别的线程通过其他方式访问,仍然要自己保证安全。

面试时担心忘词?即答侠实时助你

即答侠 AI 实时监听面试对话,自动识别问题并即时生成回答建议——无感辅助,让你从容应对每一道题。

免费试用即答侠