我们通常会用ThreadLocal用来存储当前线程的数据。但是在实际使用的时候注意使用完之后及时调用实例的remove方法。

他把数据绑定到当前线程代码原理是这么干的。

首先Thread类里面存储ThreadLocalMap。

ThreadLocal内存泄漏的缘故_ide

 然后ThreadLocalMap是ThreadLocal的内部类。

ThreadLocal内存泄漏的缘故_数据_02

可以看到ThreadLocalMap里面entry是以ThreadLocal作为key值的。

他存储数据到当前线程是这么干的。

ThreadLocal内存泄漏的缘故_内存泄漏_03

 相当于ThreadLocal本身不存储数据,而是作为key放在其内部类ThreadLocalMap中,数据作为value放在ThreadLocalMap中。

ThreadLocal内存泄漏的缘故_内存泄漏_04

要取数据的时候。通过当前线程获取其ThreadLocalMap,然后根据threadlocal实例获取对应的数据。

从上面可以看出,ThreadLocalMap是里面entry是以ThreadLocal作为key值的,而且还是弱引用的。意味着下次gc的时候,不管jvm堆内存是否足够,threadlocal都会被回收掉,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

我们复现一下ThreadLocal内存泄漏的现象。

public class Test {
    public static void main(String[] args) {
        new Thread(() -> {
            // ThreadLocal放置到代码块中,为证明threadLocal 会被GC回收掉
            {
                ThreadLocalTest<ValueVO> threadLocal = new ThreadLocalTest<>();
                threadLocal.set(new ValueVO());
                System.out.println(threadLocal.get());
            }
            int i = 0;
            while (true) {
                try {
                    Thread.sleep(1000);
                    //每秒加10M 堆内存 能看出内存增长趋势
                    //GC时,a临时变量会被回收
                    int[] a = new int[1024 * 1024 * 10];
                    if (i++ > 100) {
                        break;
                    }
                    i++;
                } catch (InterruptedException e) {
                }
            }

        }).start();
    }
}

class ValueVO {
    @Override
    public String toString() {
        return "hello,world";
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("value deading");
    }
}

/**
 * 继承ThreadLocal,实现finalize方法,垃圾回收器准备释放内存的时候,会先调用finalize()。
 *
 * @param <T>
 */
class ThreadLocalTest<T> extends ThreadLocal<T> {

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("key deading");
    }
}

 ThreadLocal内存泄漏的缘故_i++_05

 然后发现ThreadLocalMap的作为key值的ThreadLocal被垃圾回收了,作为value的当前线程的ValueVo的数据最后都没被回收。

ThreadLocal内存泄漏的缘故_数据_06

解决办法:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。