前言ThreadLocal的作用是提供线程中的局部变量,在线程的生命周期内工作,减少一些公共变量在同一线程的多个函数或组件之间的传递复杂。但是如果ThreadLocal被滥用,可能会导致内存泄漏。接下来我们将从三个方面来分析ThreadLocal内存泄漏的问题。ThreadLocal实现原理ThreadLocal为什么会泄漏内存?ThreadLocal最佳实践ThreadLocal实现原理ThreadLocal实现如下:每个Thread维护一个ThreadLocalMap映射表,映射表中的key是ThreadLocal实例本身,value是真正需要存储的Object。也就是说,ThreadLocal本身不存储值,它只是作为一个键,让线程从ThreadLocalMap中获取值。值得注意的是,图中的虚线表示ThreadLocalMap使用ThreadLocal的弱引用作为Key,弱引用对象在GC时会被回收。为什么ThreadLocal会泄漏内存?ThreadLocalMap使用ThreadLocal的弱引用作为key。如果一个ThreadLocal没有外部强引用引用它,那么在系统GC的时候这个ThreadLocal就会被回收。这样,key在ThreadLocalMap中就会为null。Entry,没有办法访问这些key为null的Entry的值。如果当前线程长时间不结束,这些key为null的Entry的值总会有一个强引用链:ThreadRef->Thread->ThreaLocalMap->Entry->value永远不能被回收,导致内存泄漏。其实在ThreadLocalMap的设计中就已经考虑到了这种情况,并增加了一些保护措施:当ThreadLocal的get()、set()、remove()时,会清除key为的线程ThreadLocalMap中的所有值无效的。但是,这些被动的预防措施并不能保证不会发生内存泄漏:使用staticThreadLocal会延长ThreadLocal的生命周期,可能会导致内存泄漏(参考ThreadLocal内存泄漏实例分析)。如果分配使用了ThreadLocal,不再调用get()、set()、remove()方法,就会造成内存泄漏。为什么要使用弱引用从表面上看,内存泄漏的根源是使用弱引用。网上的文章大多集中在分析ThreadLocal使用弱引用导致内存泄漏,但是另外一个问题也值得思考:为什么要使用弱引用而不是强引用?我们先看看官方文档:为了帮助处理非常大且长期存在的用法,哈希表条目使用Wea??kReferences作为键。为了处理非常大和长期使用,哈希表使用弱引用键。下面我们讨论两种情况:Key使用强引用:被引用的ThreadLocal对象被回收,但是ThreadLocalMap仍然持有ThreadLocal的强引用。如果不手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。key使用弱引用:被引用的ThreadLocal对象被回收。由于ThreadLocalMap持有ThreadLocal的弱引用,所以即使不手动删除ThreadLocal也会被回收。下次ThreadLocalMap调用set、get和remove时,该值将被清除。对比两种情况,我们可以发现:由于ThreadLocalMap的生命周期和Thread一样长,如果不手动删除对应的key,会造成内存泄漏,但是使用弱引用可以多一层保护:ThreadLocal的弱引用不会造成内存泄漏,下次ThreadLocalMap调用set、get、remove时会清空对应的值。因此,ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期与Thread一样长,如果不手动删除对应的key,就会造成内存泄漏,并不是因为弱引用。**ThreadLocalBestPractice根据上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么如何避免内存泄漏呢?**每次使用ThreadLocal时,都会调用其remove()方法来清除数据。在使用线程池的情况下,如果不及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的可能会导致业务逻辑出现问题。所以,使用ThreadLocal就相当于加锁后解锁,使用后清理。
