我们通常会用ThreadLocal用来存储当前线程的数据。但是在实际使用的时候注意使用完之后及时调用实例的remove方法。
他把数据绑定到当前线程代码原理是这么干的。
首先Thread类里面存储ThreadLocalMap。
然后ThreadLocalMap是ThreadLocal的内部类。
可以看到ThreadLocalMap里面entry是以ThreadLocal作为key值的。
他存储数据到当前线程是这么干的。
相当于ThreadLocal本身不存储数据,而是作为key放在其内部类ThreadLocalMap中,数据作为value放在ThreadLocalMap中。
要取数据的时候。通过当前线程获取其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"); } }
然后发现ThreadLocalMap的作为key值的ThreadLocal被垃圾回收了,作为value的当前线程的ValueVo的数据最后都没被回收。
解决办法:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。