我们通常会用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()方法,清除数据。