ThreadLocal的内存泄露问题

ThreadLocal的内部实现

在每一个线程Thread对象中,都维护了一个ThreadLocalMap对象。
在这里插入图片描述
ThreadLocalMap中又维护了一个k v 形式的Entry对象,key指向了当前ThreadLocal对象,value就是我们实际在ThreadLocal中存储的值。
在这里插入图片描述

注意,这里的Entry中的key存放是ThreadLocal的弱引用。

在这里插入图片描述

实现指的是强引用,虚线指的是弱引用。

其实际上,ThreaLocal本身是不存储值的,我们在使用其对应的set、get方法时,都是操作的其对应的ThreadLocalMap对象。

为什么会出现内存泄露?

从上述可以看到,在Entry中的key存储的ThreadLocal的弱引用

弱引用在发生GC时,就会被垃圾回收掉,具体可以参考JVM相关的知识。

所以,在当前线程正在运行的时候,发生GC时,在ThreadLocal对象没有被其它地方强引用时,key指向ThreadLocal的虚引用就会立即断开(被垃圾回收掉),这时,就会出现ThreadLocalMap中存在key为null的Entry,只要当前线程不结束,该ThreadLocalMap对象就会一直存在,永远无法回收,因为此时还存在一条强引用的链路,从图中也可以发现:

Current Thread Reference --> Current Thread --> ThreadLocalMap --> EntryValue --> Object

所以这个时候就造成了内存泄露。

Entry对象的key为什么要使用弱引用,有什么好处?

在上述所说的问题中,即使ThreadLocalMap中存在key为null的Entry,但是该Entry的value值并不会因为GC而被回收(value存本身就存着一个强引用的对象),所以就导致了该对象不会被回收掉而出现了内存泄露。

其实,ThreadLocalMap在设计时就考虑到了这个方面,它也采取了一些措施来避免这种key为null,而value不为null的对象占用内存,在我们调用ThreadLocal的set、get、remove方法时,都会将这些key为null的对象清空掉,避免因为这种情况而导致内存泄露。

这也就是为什么key要存储弱引用的原因。

假设如果存储的强引用,我们断开ThreadLocal Reference —> ThreadLocal的引用,会发现key强引用了ThreadLocal,导致该对象永远无法被GC。
在这里插入图片描述
但是,即使上述提供了避免内存泄露的措施,但是不能完全避免,比如以下的情况:

  • 分配了ThreadLocal对象,但是并没有执行其get、set、remove方法,导致不能有效的清除null对象;
  • 使用线程池的情况下,使用完ThreadLocal一定要使用remove方法即时清理,因为ThreadLocal是属于某个线程的,而在使用线程池的情况下,这些线程都是可重复利用、存活时间长的线程,如果在使用过程中不仅从即使的remove,那么不仅会造成内存泄露的问题,还会引发一些功能逻辑问题,比如,B请求可能和A请求分配到了线程池中的同一个线程,那么它们拿到的ThreadLocal就是一样的。

set
在这里插入图片描述
cleanSomeSlots
在这里插入图片描述
get
在这里插入图片描述

关于弱引用的一些知识补充

学习的过程中想到了一个问题,弱引用会不会导致运行过程中GC清除key,导致找不到对应的value?

可能是当时对弱引用的理解不够熟,所以产生了这个问题,如下面的代码。

public class TestDemo {

    static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) {

        threadLocal.set("demo");
        System.gc();
        System.out.println(threadLocal.get());	// demo

    }
}

为什么还获取到值,不是说在发现一次GC,弱引用就会被清除掉吗?

糊涂了。

弱引用只有在该对象没有被其它地方强引用的时候,才会被GC。

上述的原因就是因为,很明显,ThreadLocal对象除了被key弱引用,还由一个Reference强引用指向它,所以肯定不会被GC。

在这里插入图片描述
如果是这样,那下一次GC,这个对象就被干掉了。
在这里插入图片描述

举一个简单的例子,帮助理解:

WeakReference<User> userWeakReference = new WeakReference<User>(new User("jack"));

System.out.println(userWeakReference.get() == null);  // false

System.gc();

System.out.println(userWeakReference.get() == null);  // true

很明显,GC的时候直接清除了这个弱引用对象。

userWeakReference.get(), 如果此方法为空, 那么说明weakReference指向的对象已经被回收了。

WeakReference<User> userWeakReference = new WeakReference<User>(new User("jack"));
User jack = userWeakReference.get();
System.out.println(userWeakReference.get() == null);  // false

System.gc();

System.out.println(userWeakReference.get() == null);  // false

当我们添加了一个强引用来指向它的时候,该对象并不会被gc清除(弱引用还在)。

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值