java的threadlocal为何内存泄漏

ThreadLocal 的实现原理:每一个 Thread 维护一个 ThreadLocalMap,key 为使用弱引用的 ThreadLocal 实例,value 为线程变量的副本,这些对象之间的引用关系如下:

实心箭头表示强引用,虚心箭头表示弱引用

ThreadLocal 的内存泄露发生在 Entry 上,我们现在来详细分析 Entry。

对于 Entry 的 key 来说,它是 ThreadLocal 对象,它有两个引用源,一个是栈内存上的 ThreadLocal Ref,一个是 Entry 中的 key,如下:

对于 Entry 的 value 而言它就只有一条引用链:

对于 Entry 来说,由于存在 key 和 value 两个引用路径,所以这里就会有两种情况:

栈上的 ThreadLocal Ref 不再使用了,但是由于 ThreadLocal 对象还有一条引用链存在,这就会导致它无法被回收,时间久了就会导致内存泄露。

由于线程池的存在,会让线程一直被重复利用,就会导致第二条 value 链一直存在,导致 ThreadLocalMap 无法被回收,从而导致内存泄露。

内存泄露的解决方案

第一种情况:弱引用

栈上的 ThreadLocal Ref 不再使用了,但是由于 ThreadLocal 对象还有一条引用链存在,这就会导致它无法被回收,时间久了就会导致内存泄露。

为了解决这种情况,ThreadLocal 使用了弱引用。即上图用虚线表示的部分。源代码如下:

tatic class ThreadLocalMap {

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

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

}

对 JVM 熟悉的小伙伴知道,如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器回收掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。

当堆栈上的 ThreadLocal Ref 不再使用了,ThreadLocal 对象就只有弱引用了,那么 ThreadLocal 对象就可以在下次GC时被回收掉了。

第二种情况:使用完 ThreadLocal 后调用 remove()

由于线程池的存在,会让线程一直被重复利用,就会导致第二条 value 链一直存在,导致 ThreadLocalMap 无法被回收,从而导致内存泄露。

value 的生命周期与 Thread 是一样的,由于线程池的存在,导致线程一直都被重复利用,从而导致 value 对象一直都无法被释放。那怎么解决呢?

我们知道 ThreadLocal 本身其实是不存储数据的,数据都是存在 ThreadLocalMap 中的,所以我们在每次调用ThreadLocal的get()、set()、remove() 方法的时候,内部实际会调用ThreadLocalMap的get()、set()、remove() 操作。而 ThreadLocalMap 的这些方法都会清理key为null,但是value还存在的Entry。

所以,当我们在一个 ThreadLocal 用完之后,可以手动调用一下remove(),就可以在下一次GC的时候,把Entry清理掉。

总结

由于 Thread 中包含了 ThreadLocalMap 的变量,因此 ThreadLocalMap 与 Thread 的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。

但是,由于 ThreadLocal 使用了弱引用,则多了一层保障:弱引用 ThreadLocal 不会内存泄漏,因为它会在下一次 GC 时被回收。

ThreadLocal 内存泄漏的根源是:Thread 被重复利用,导致 value 强引用链一直存在,而导致内存泄露,注意,不是因为弱引用。

所以,当我们用完一个 ThreadLocal 后,可以手动调用一下remove(),就可以在下一次GC的时候,把Entry清理掉。

为什么我们在使用 ThreadLocal 的时候,一再强调要手动调用 remove() 方法。

相关推荐
xing-xing31 分钟前
JVM 内存、直接内存、系统内存、本地内存、物理内存总结
java·jvm
yangpipi-31 分钟前
《C++并发编程实战》第5章 C++内存模型和原子操作
android·java·c++
qq_12498707531 小时前
基于微信小程序的电子元器件商城(源码+论文+部署+安装)
java·spring boot·spring·微信小程序·小程序·毕业设计
吃喝不愁霸王餐APP开发者2 小时前
基于Spring Cloud Gateway实现对外卖API请求的统一鉴权与流量染色
java·开发语言
a努力。2 小时前
美团Java面试被问:Redis集群模式的工作原理
java·redis·后端·面试
一雨方知深秋2 小时前
面向对象编程
java·封装·this·构造器·static关键字·成员变量·javabean实体类
资生算法程序员_畅想家_剑魔2 小时前
Java常见技术分享-11-责任链模式
java·spring boot·责任链模式
计算机程序设计小李同学2 小时前
动漫之家系统设计与实现
java·spring boot·后端·web安全
程序员阿鹏3 小时前
责任链模式
java·spring·servlet·tomcat·maven·责任链模式
@淡 定3 小时前
Java内存模型(JMM)详解
java·开发语言