首先来看ThreadLocal中的源码,这是ThreadLocal中的set方法
它首先通过Thread.currentThread()方法去获取当前线程,然后将当前线程t传到getMap方法里面。
跟进去getMap方法之后我们发现,t.threadLocals调用的是其实线程Thread类里面的一个局部变量ThreadLocal.ThreadLocalMap。
也就是说每个Thread线程都有自己独有的一个Map叫ThreadLocalMap,这也就是为什么ThreadLocal能保证线程互不干扰的原因。因为每个Thread线程操作的只是自己的Map。
再返回去看ThreadLocals的set方法,一开始Thread里面的ThreadLocalMap属性是为null的,那么它就通过createMap()方法去创建一个线程独有的ThreadLocalMap。
那么我们再跟进来看看这个创建的这个ThreadLocalMap有什么秘密吧。
这段代码的逻辑最终作用是——把该ThreadLocal对应的值存在一个成员变量table里,以key/value的形式存储,key是当前的ThreadLocal实例,value就是我们要保存的值
我们发现,ThreadLocalMap创建的时候,创建的Entry继承了弱引用。
弱引用的特点就是只有有GC,不管内存够不够它都会被GC所回收
类型 | 回收时间 | 应用场景 |
---|---|---|
强引用 | 一直存活,除非GC Roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
软引用 | 内存不足时会被回收 | 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存 |
弱引用 | 只能存活到下一次GC前 | 生命周期很短的对象,例如ThreadLocal中的Key。 |
虚引用 | 随时会被回收, 创建了可能很快就会被回收 | 可能被JVM团队内部用来跟踪JVM的垃圾回收活动 |
之所以不使用其他的强引用,软引用等,是因为:
强引用,即使key为null,但key的引用依然指向ThreadLocal对象,所以会发生内存泄漏。
软引用是内存不够才会被GC回收,它更适合缓存的使用场景。
虚引用是用来管理直接内存GC的。
只要你了解了弱引用的概念,那么就会知道,原来ThreadLocal发生内存泄漏的原因就是在这里了。
因为ThreadLocal被回收后,key为null,导致value再也无法被访问,那怎么办呢?只能是把整个entry给remove掉,而不是仅仅回收key。事实上,ThreadLocal在调用set()和get()方法时会自动remove掉key为null的entry。但是如果不执行set()和get()就会存在泄漏。所以我们要养成手动remove的习惯