ThreadLocal
导致内存泄漏的主要原因是它的工作方式。在 Java 中,ThreadLocal
通过维护一个以 Thread
为键,以用户设置的值为值的映射来工作。每个线程都拥有其自身的线程局部变量副本,不同线程间的这些变量互不干扰。这个映射是存储在每个 Thread
对象的一个 ThreadLocal.ThreadLocalMap
字段里。
当使用 ThreadLocal
时,以下两个因素相互作用可能导致内存泄漏:
-
ThreadLocalMap
使用的是ThreadLocal
对象的弱引用
每个线程的ThreadLocalMap
中使用ThreadLocal
对象作为键值,并且这些键值是以“弱引用” (WeakReference) 的形式存储的。如果ThreadLocal
对象没有其他强引用,那么它可能会在垃圾收集期间被收集。然而,虽然键值引用可能消失了,但键值相关联的值却可能仍然留在ThreadLocalMap
中。值对应的对象由于还被ThreadLocalMap
强引用着,因此不会彻底被GC掉,这样就可能造成内存泄漏。 -
线程的生命周期
下一个令ThreadLocal
问题复杂化的因素是线程的生命周期。如果采用线程池,线程一般会被复用,即线程并不会结束生命周期。这意味着除非线程被终结,或者ThreadLocalMap
中的条目被清除,这部分内存就一直不会释放。
解决ThreadLocal
内存泄露的措施主要包含以下几点:
-
及时移除
最简单的解决方式是,在不再需要访问ThreadLocal
存储的数据时,及时调用ThreadLocal.remove()
方法来清除线程局部变量。 -
使用try-finally块确保remove()
在代码结构中采用try-finally
块,在try
块中访问线程局部变量,然后在finally
块中调用remove()
。这样即使出现异常,也能保证局部变量被清除。 -
自定义ThreadLocal匿名内部类
创建ThreadLocal
变量时,覆盖ThreadLocal
的initialValue()
和remove()
方法,以确保在书写代码时关注资源的清理。 -
减少使用ThreadLocal
如果能不使用ThreadLocal
以避免其中的陷阱,则通过其他方式传递方法间共享的数据也是一种办法。 -
线程池监控
监控使用线程池时线程的回收情况,当确定线程不会再被复用,清除它所持有的ThreadLocal
变量。
通过漏斗管理 Best practice 以及理解 ThreadLocal
的工作方式,可以减少或防止内存泄露问题的发生。