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

相关推荐
蒸蒸yyyyzwd2 小时前
cpp对象模型学习笔记1.1-2.8
java·笔记·学习
程序员徐师兄3 小时前
Windows JDK11 下载安装教程,适合新手
java·windows·jdk11 下载安装·jdk11 下载教程
RANCE_atttackkk4 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
五岳4 小时前
DTS按业务场景批量迁移阿里云MySQL表实战(下):迁移管理平台设计与实现
java·应用·dts
zhougl9965 小时前
Java 所有关键字及规范分类
java·开发语言
Python 老手5 小时前
Python while 循环 极简核心讲解
java·python·算法
java1234_小锋5 小时前
Java高频面试题:MyISAM索引与InnoDB索引的区别?
java·开发语言
Mr_Xuhhh5 小时前
MySQL函数详解:日期、字符串、数学及其他常用函数
java·数据库·sql
测试开发Kevin6 小时前
小tip:换行符CRLF 和 LF 的区别以及二者在实际项目中的影响
java·开发语言·python
笨手笨脚の6 小时前
Redis: Thread limit exceeded replacing blocked worker
java·redis·forkjoin·thread limit