一、问题分析
测试用例
Thread thread = Thread.currentThread();
ThreadLocal<Integer> tl = new ThreadLocal<>();
tl.set(1);
System.gc(); // 手动提醒下gc
Thread.sleep(100); // 让gc先执行
System.out.println(111);
tl = null; // 断开ThreadLocal引用
System.gc();
Thread.sleep(100);
System.out.println(111); // 断点打这里
可以看到线程中的threadLocals还持有这个ThreadLocalMap的引用,其中Entry中value还是有值的为1,但是referent为null,表示已经被GC
这里可以看Entry的实现
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
public abstract class Reference<T> {
private T referent; /* Treated specially by GC */
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
}
简单来说:ThreadLocalMap还持有Entry对象的引用,所以Entry对象并不会被释放。而由于key是WeakReference中的referent,对于Entry->referent这个关联引用会在gc的时候断开,所以可以看到上面的referent为null,但是ThreadLocalMap中还存在entry对象。所以这样value是会一直被引用的,可能会导致内存溢出的问题存在。
二、如何避免
我们可以使用 remove来避免这种情况
Thread thread = Thread.currentThread();
ThreadLocal<Integer> tl = new ThreadLocal<>();
tl.set(1);
System.gc(); // 手动提醒下gc
Thread.sleep(100); // 让gc先执行
System.out.println(111);
tl.remove(); // 调用remove
tl = null; // 断开ThreadLocal引用
System.gc();
Thread.sleep(100);
System.out.println(111); // 断点打这里
这里table里面只有3个了,说明已经被回收了。
ThreadLocal.remove()方法其实就是获取当前线程的threadLocals(ThreadLocalMap)并调用其remove()方法
ps: 可以对照set方法看看实现,这里解决hash冲突的话不是和HashMap一样用拉链法,而是去找下一个没有设置值得结点
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) { // 找到对应的ThreadLocal
e.clear(); // this.referent = null; 断开引用
expungeStaleEntry(i);
return;
}
}
}
继续看 expungeStaleEntry 的实现
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将Entry的value也就是ThrealLocal对应的值置为null
// 断开tab是对应槽位和Entry的关联
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 下面是rehash操作不是本次重点
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// ......
}
return i;
}