为什么用完TL之后都要求调用remove方法?

91 阅读2分钟

key泄漏

ThreadLocal instance = null  

我们想要清理TL实例,假设我们再ThreadLocalMap的Entry中强引用了TL实例,那么虽然在业务代码中虽然置null,但是Thread类仍然有这个引用链存在,GC的时候发现可达,不回收,造成内存泄漏

上源码

ThreadLocal TLM Entry

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
​
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

弱引用不会阻止GC,由此可见,这个弱引用避免了TL内存泄漏问题

value泄漏

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
​
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        //强引用
        value = v;
    }
}

虽然keyok,但是value是强引用

正常情况下,当线程终止,key 所对应的 value 是可以被正常垃圾回收的,因为没有任何强引用存在了。但是有时线程的生命周期是很长的,如果线程迟迟不会终止,那么可能 ThreadLocal 以及它所对应的 value 早就不再有用了。在这种情况下,我们应该保证它们都能够被正常的回收。

image.png

这条链路是随着线程的存在而一直存在的,如果线程执行耗时任务而不停止,那么当垃圾回收进行可达性分析的时候,这个 Value 就是可达的,所以不会被回收。但是与此同时可能我们已经完成了业务逻辑处理,不再需要这个 Value 了,此时也就发生了内存泄漏问题。 JDK 同样也考虑到了这个问题,在执行 ThreadLocal 的 set、remove、rehash 等方法时,它都会扫描key 为 null 的 Entry,如果发现某个 Entry 的 key 为 null,则代表它所对应的 value 也没有作用了,所以它就会把对应的 value 置为 null,这样,value 对象就可以被正常回收了

如何避免内存泄漏?

public void remove() {
    //获取当前线程的ThreadLocalMap
    ThreadLocalMap m = getMap(Thread.currentThread());
    //ThreadLocalMap不为空则remove,这时候key对应的value都会被清理掉
    if (m != null) {
        m.remove(this);
    }
}
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) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

由此可见value被GC回收了

总结

从源码分析来看TL调用remove是为了避免内存泄漏,就酱