ThreadLocal的原理:
ThreadLocal的set实际实在当前线程对象里创建了一个内部变量ThreadLocalMap<ThreadLocal,object> ,ThreadLocalMap的key是ThreadLocal的引用。
造成泄漏的原因:
由于ThreadLocal对象是弱引用,如果外部没有强引用指向它,它就会被GC回收,导致Entry的Key为null
如果当前的情况下在栈中将threadlocal1的引用设置为null,强引用1将会失效,那堆中的threadlocal1对象因为ThreadLocalMap的key对它的引用是弱引用,将会在下一次gc被回收,那就会出现key变成null,如果这时value外部也没有强引用指向它,那么value就永远也访问不到了,按理也应该被GC回收,但是由于ThreadLocalMap.Entry对象还在强引用value,导致value无法被回收,这时「内存泄漏」就发生了,value成了一个永远也无法被访问,但是又无法被回收的对象。
解决办法:
1:将ThreadLocal设置为空之前,执行remove()方法,会将key为空的键值对清空
2:尽量将ThreadLocal设置成static
3: 非必要尽量不要在ThreadLocal中放大对象
ThreadLocal做出的努力
ThreadLocal不是洪水猛兽,不要听到「内存泄漏」就不敢使用它,只要你规范化使用是不会有问题的。再者,就算你不规范使用,ThreadLocal也做出了很多努力来最大程度的帮你避免发生「内存泄漏」。
前面已经说过,由于Key是弱引用,因此ThreadLocal可以通过key.get()==null来判断Key是否已经被回收,如果Key被回收,就说明当前Entry是一个废弃的过期节点,ThreadLocal会自发的将其清理掉。
hreadLocal会在以下过程中清理过期节点:
调用set()方法时,采样清理、全量清理,扩容时还会继续检查。 调用get()方法,没有直接命中,向后环形查找时。 调用remove()时,除了清理当前Entry,还会向后继续清理。
为什么这里要用弱引用:
网上有的文章将ThreadLocal内存泄漏的原因怪罪于Entry的Key的弱引用,这个说法是极其错误的!
不用弱引用就能避免「内存泄漏」了吗?当然不是!!! 恰恰相反,使用弱引用是JDK在尽量避免程序出现「内存泄漏」,如下代码:
代码语言:javascript复制public class Test {
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set(new Object());
threadLocal = null;
}
}
创建一个ThreadLocal对象,并设置一个Object对象,然后将其置空。如果Key不是弱引用的话,threadLocal无法被回收,也无法被访问,object无法被回收,也无法被访问,Key和Value同时出现了「内存泄漏」