ThreadLocal内存泄露原因分析,这些情况需警惕
大家好,我是大明哥,一个专注「死磕 Java」系列创作的硬核程序员。
哪些地方可能存在内存泄露
的实现原理:每一个 维护一个 ,key 为使用弱引用的 实例,value 为线程变量的副本,这些对象之间的引用关系如下:

实心箭头表示强引用,虚心箭头表示弱引用
的内存泄露发生在 Entry 上,我们现在来详细分析 Entry。
对于 Entry 的 key 来说,它是 对象,它有两个引用源,一个是栈内存上的 Ref,一个是 Entry 中的 key,如下:

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

对于 Entry 来说,由于存在 key 和 value 两个引用路径,所以这里就会有两种情况:
栈上的 Ref 不再使用了,但是由于 对象还有一条引用链存在,这就会导致它无法被回收,时间久了就会导致内存泄露。由于线程池的存在,会让线程一直被重复利用,就会导致第二条 value 链一直存在,导致 无法被回收,从而导致内存泄露。内存泄露的解决方案第一种情况:弱引用
栈上的 Ref 不再使用了,但是由于 对象还有一条引用链存在,这就会导致它无法被回收,时间久了就会导致内存泄露。
为了解决这种情况, 使用了弱引用。即上图用虚线表示的部分。源代码如下:
tatic class ThreadLocalMap {
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
...
}

对 JVM 熟悉的小伙伴知道,如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器回收掉(被弱引用所引用的对象只能生存到下一次GC之前,当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉)。
当堆栈上的 Ref 不再使用了, 对象就只有弱引用了,那么 对象就可以在下次GC时被回收掉了。
第二种情况:使用完 后调用 ()
由于线程池的存在,会让线程一直被重复利用,就会导致第二条 value 链一直存在,导致 无法被回收,从而导致内存泄露。
value 的生命周期与 是一样的,由于线程池的存在,导致线程一直都被重复利用,从而导致 value 对象一直都无法被释放。那怎么解决呢?
我们知道 本身其实是不存储数据的,数据都是存在 中的,所以我们在每次调用的get()、set()、() 方法的时候,内部实际会调用的get()、set()、() 操作。而 的这些方法都会清理key为null,但是value还存在的Entry。
所以,当我们在一个 用完之后,可以手动调用一下(),就可以在下一次GC的时候,把Entry清理掉。
总结
由于 中包含了 的变量,因此 与 的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。
但是,由于 使用了弱引用,则多了一层保障:弱引用 不会内存泄漏,因为它会在下一次 GC 时被回收。
内存泄漏的根源是: 被重复利用,导致 value 强引用链一直存在,而导致内存泄露,注意,不是因为弱引用。
所以,当我们用完一个 后,可以手动调用一下(),就可以在下一次GC的时候,把Entry清理掉。
为什么我们在使用 的时候,一再强调要手动调用 () 方法。
本文已收录到我的技术网站:。有全网最优质的系列文章、Java 全栈技术文档以及大厂完整面经
























