ThreadLocal
的做用是提供线程内的局部变量,这种变量在线程的生命周期内起做用,减小同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。可是若是滥用 ThreadLocal
,就可能会致使内存泄漏。下面,咱们将围绕三个方面来分析 ThreadLocal
内存泄漏的问题函数
ThreadLocal
实现原理ThreadLocal
为何会内存泄漏ThreadLocal
最佳实践
ThreadLocal
的实现是这样的:每一个Thread
维护一个 ThreadLocalMap
映射表,这个映射表的 key
是 ThreadLocal
实例自己,value
是真正须要存储的 Object
。spa
也就是说 ThreadLocal
自己并不存储值,它只是做为一个 key
来让线程从 ThreadLocalMap
获取 value
。值得注意的是图中的虚线,表示 ThreadLocalMap
是使用 ThreadLocal
的弱引用做为 Key
的,弱引用的对象在 GC 时会被回收。.net
ThreadLocal
为何会内存泄漏ThreadLocalMap
使用ThreadLocal
的弱引用做为key
,若是一个ThreadLocal
没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal
势必会被回收,这样一来,ThreadLocalMap
中就会出现key
为null
的Entry
,就没有办法访问这些key
为null
的Entry
的value
,若是当前线程再迟迟不结束的话,这些key
为null
的Entry
的value
就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远没法回收,形成内存泄漏。线程
其实,ThreadLocalMap
的设计中已经考虑到这种状况,也加上了一些防御措施:在ThreadLocal
的get()
,set()
,remove()
的时候都会清除线程ThreadLocalMap
里全部key
为null
的value
。设计
可是这些被动的预防措施并不能保证不会内存泄漏:code
static
的ThreadLocal
,延长了ThreadLocal
的生命周期,可能致使的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。ThreadLocal
又再也不调用get()
,set()
,remove()
方法,那么就会致使内存泄漏。从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal
使用了弱引用会致使内存泄漏,可是另外一个问题也一样值得思考:为何使用弱引用而不是强引用?对象
咱们先来看看官方文档的说法:blog
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对很是大和长时间的用途,哈希表使用弱引用的 key。生命周期
下面咱们分两种状况讨论:内存
ThreadLocal
的对象被回收了,可是ThreadLocalMap
还持有ThreadLocal
的强引用,若是没有手动删除,ThreadLocal
不会被回收,致使Entry
内存泄漏。ThreadLocal
的对象被回收了,因为ThreadLocalMap
持有ThreadLocal
的弱引用,即便没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。比较两种状况,咱们能够发现:因为ThreadLocalMap
的生命周期跟Thread
同样长,若是都没有手动删除对应key
,都会致使内存泄漏,可是使用弱引用能够多一层保障:弱引用ThreadLocal
不会内存泄漏,对应的value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。
所以,ThreadLocal
内存泄漏的根源是:因为ThreadLocalMap
的生命周期跟Thread
同样长,若是没有手动删除对应key
就会致使内存泄漏,而不是由于弱引用。
综合上面的分析,咱们能够理解ThreadLocal
内存泄漏的来龙去脉,那么怎么避免内存泄漏呢?
ThreadLocal
,都调用它的remove()
方法,清除数据。在使用线程池的状况下,没有及时清理ThreadLocal
,不只是内存泄漏的问题,更严重的是可能致使业务逻辑出现问题。因此,使用ThreadLocal
就跟加锁完要解锁同样,用完就清理。