ThreadLocal造成的内存泄漏

在多线程中为了避免线程安全问题,常用的一种方式就是引入ThreadLocal变量,为何这种方式会引发线程安全问题呢?

首先我们来看一下ThreadLocal是如何实现保存线程私有变量的原理:

ThreadLocal里面定义了一个内部类ThreadLocalMap

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

....

}

这个ThreadLocalMap是关键,再查看Thread源码,我们可以看到

每个Thread持有一个初始值为null的ThreadLocalMap变量

ThreadLocal造成的内存泄漏_第1张图片

在ThreadLocal变量调用get、set方法时,会对当前线程的ThreadLocalMap进行初始化,那么这时会做什么操作呢

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
 }

可以看到ThreadLocal变量将自身作为参数构造了线程持有的ThreadLocalMap,而这里面初始化了一个Entry数组类型的table,并且ThreadLocal变量是作为key使用的。是不是有点眼熟,不过这个Entry是ThreadLocalMap里的一个内部类Entry,从上面源码中可以看到Entry继承了WeakReference,这是造成内存泄漏的关键。WeakReference指的弱引用,弱引用的定义是,当jvm执行内存回收时发现该某个对象只有弱引用时,会被回收。那么问题来了,当某个生存周期长的线程运行过程中,某个定义的ThreadLocal强引用变成了null,只剩线程里的ThreadLocalMap里持有的弱引用,gc发生时,该Entry的key指向的对象将被回收,可是强引用value指向的对象是无法回收的,就造成了内存泄漏。

ThreadLocal造成的内存泄漏_第2张图片

解决方法:当某个ThreadLocal变量不再使用时,记得threadLocal.remove(),删除该key。

 

你可能感兴趣的:(ThreadLocal造成的内存泄漏)