ThreadLocal内存泄露问题
了解ThreadLocal内存泄露问题首先要了解 1.ThreadLocal是什么?2ThreadLocal.有什么用?3.java的四种引用类型,然后你就理解了ThreadLocal为什么会发生内存泄露问题了。
1. ThreadLocal是什么?
ThreadLocal,即 本地线程变量 ,是一个以ThreadLocal对象为键(key)、任意对象为值(value)的存储结构。每个线程Thread
拥有一份自己的本地线程变量 ,多个线程互不干扰。
1.1 ThreadLocal有什么用?
- 如果你创建了一个
ThreadLocal
变量,那么访问这个变量的 每个线程 都会有这个变量的本地副本,这也是ThreadLocal
变量名的由来。他们可以使用get()
和set()
方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 - ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
1.2 ThreadLocal源码分析
Thread类部分源码:
-
Thread 类中有一个 ThreadLocalMap 类型的变量:
threadLocals
-
ThreadLocalMap 底层数据结构是由一个
Entry[]
数组组成,Entry对象用来保存key/value, 它的 key 是 ThreadLocal<?> k ,继承自WeakReference
(弱引用类型,发生GC时会被回收)public class Thread implements Runnable { 省略... //与此线程有关的ThreadLocal值。由ThreadLocal类维护 ThreadLocal.ThreadLocalMap threadLocals = null; }
ThreadLocal类部分源码:
-
通过 ThreadLocal 对象调用
set()
方法的源码会发现,实际上是获取当前线程后拿到当前线程的threadLocals
成员变量 -
每个线程往 ThreadLocal 里 set 值的时候,都会往自己的
ThreadLocalMap
里存,读也是以 ThreadLocal 作为引用,在自己当前线程的 ThreadLocalMap 里找对应的 key ,从而实现了 线程隔离 。public class ThreadLocal<T> { ... public void set(T value) { Thread t = Thread.currentThread(); //取出 Thread 类内部的 threadLocals 变量(哈希表结构) ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } }
1.3 了解Java的四种引用类型
- 强引用: new出来的对象就是强引用类型,只要强引用存在 GC将永远不会回收被引用的对象,哪怕内存不足的时候
- 软引用:使用
SoftReference
修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收 - 弱引用:使用
WeakReference
修饰的对象被称为弱引用,弱引用指向的对象只要发生 GC 就会被回收 - 虚引用:使用
PhantomReference
进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知
2. ThreadLocal造成内存泄露的原因
ThreadLocal 中有个静态内部类是 ThreadLocalMap ,它里面定义了 Entry 对象来保存数据且继承的是弱引用WeakReference
。在 Entry 内部使用 ThreadLocal 作为 key ,使用我们设置的值作为 value 。(在构造方法中,将key设置成了弱引用)。如果ThreadLocal是null了,也就是要被GC回收了,但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
/**
* 构造Entry对象时,将key(ThreadLocal)对象设置为了弱引用
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
...
}
2.1 为什么ThreadLocal的key要使用弱引用?
🐸 ThreadLocalMap 是线程的成员变量,只要线程还未结束,则会一直被Thread
强引用着,假设 Entry 对象的 key 是对 ThreadLocal 对象的强引用,如果在其他地方都没有对这个 ThreadLocla 对象的引用了,然后在使用ThreadLocalMap
的过程中又没有正确地在用完后就调用 remove 方法,所以这个 ThreadLocal 对象和所关联的 value 对象就会跟随着线程一直存在,这样可能会造成OOM。
2.2 为什么ThreadLocal的value不能使用弱引用?
🐸【假设Entry 的 value 是弱引用】:假设 key 所引用的 ThreadLocal 对象还被其他的引用对象强引用着,那么这个 ThreadLocal 对象就不会被 GC 回收,但如果 value 是弱引用且不被其他引用对象引用着,那 GC 的时候就被回收掉了,那线程通过 ThreadLocal 来获取 value 的时候就会获得 null,对我们来说,value 才是我们想要保存的数据,ThreadLcoal 只是用来关联 value 的,如果 value 都没了,还要 ThreadLocal 干嘛呢?所以 value 不能是弱引用。
☘️ 那么问题来了,我们了解了ThreadLocal
的key
会过期因而会造成内存泄露问题,那么ThreadLocal
又是如何处理这些过期Key
的呢?且看:ThreadLocalMap.key到期之探测是清理+启发式清理流程 - lihewei - 博客园 (cnblogs.com)