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() 方法。

相关推荐
南极星10051 天前
我的创作纪念日--128天
java·python·opencv·职场和发展
郝学胜-神的一滴1 天前
超越Spring的Summer(一): PackageScanner 类实现原理详解
java·服务器·开发语言·后端·spring·软件构建
摇滚侠1 天前
Java,举例说明,函数式接口,函数式接口实现类,通过匿名内部类实现函数式接口,通过 Lambda 表达式实现函数式接口,演变的过程
java·开发语言·python
打工的小王1 天前
java并发编程(七)ReentrantReadWriteLock
java·开发语言
lang201509281 天前
Java并发革命:JSR-133深度解析
java·开发语言
abluckyboy1 天前
基于 Java Socket 实现多人聊天室系统(附完整源码)
java·开发语言
Re.不晚1 天前
JAVA进阶之路——数据结构之线性表(顺序表、链表)
java·数据结构·链表
毅炼1 天前
Java 基础常见问题总结(3)
java·开发语言
亓才孓1 天前
[JDBC]事务
java·开发语言·数据库
CHU7290351 天前
直播商城APP前端功能全景解析:打造沉浸式互动购物新体验
java·前端·小程序