老生常谈ThreadLocal内存泄露问题

ThreadLocal内存泄露问题

关系示意

image-20241214105231281

问题分析

我们知道ThreadLocal中的值实际是和线程绑定的,从上面的关系示意中我们可以看到我们放在set到ThreadLocal中的值实际被放到了ThreadLocalMap的Entry中作为key,这个key从源码中我们可以看到是一个弱引用,当我们使用的ThreadLocal引用使用结束说,ThreadLocal对象 会被回收,但这并不意味着整个Entry会被回收,因为Entry实际还被Thread所引用,在实际应用中Thread通常被线程池管理重复使用除非重启几乎不会销毁。Entry的key由于是弱引用,最终会在下次GC被回收,但value而不然,此时就发生了所谓的**内存泄露**

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

问题与思考

为什么Entry的key要使用弱引用?

这其实就是利用弱引用的特点,它始终会下一次GC被回收,在thread是线程池可复用的情况下,entry始终被强引用,如果key也使用强引用将导致它除非被手动回收,否则将永远驻留在内存,而使用弱引用则可以利用GC将其自动回收

Entry中的key被弱引用,那它岂不是一定会在下次GC被回收,那我们怎么通过key找到我们保存的object呢?

注意,key不仅仅仅被Entry弱引用,还被我们自己写的使用ThreadLocal所强引用,所以在我们写ThreadLocal被销毁前,Entry中的key并不会被GC回收!

如果避免这个问题

在使用完ThreadLocal手动调用的remove(ps:就算没有内存泄露的问题,也应用这样做,避免线程复用时获取到错误的数据)

ps:实际ThreadLocal自己也会清理key为null的value在调用get时获取不到(但实际情况下除非发生Hash冲突,几乎很少走到这里)

public T get() {
    	...
        ThreadLocalMap.Entry e = map.getEntry(this);
        ...
}
private Entry getEntry(ThreadLocal<?> key) {
    // 通过hash确定位置
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
        
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
		...
        if (k == null)
            // 如果key为空,会把对应的value也设置为null,使其在下次GC被回收
            expungeStaleEntry(i);
		...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值