2019-03-29 周五 10:45 下午
- ThreadLocal 回顾:
主要看 ThreadLocal#get()方法:public T get() { Thread t = Thread.currentThread(); // 拿到当前线程中保存的 map ThreadLocalMap map = getMap(t); if (map != null) { // 以 this (也就是用户创建的 ThreadLocal 对象)为 key // 获取到一个 Entry 对象,然后返回 value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 当 map 为 null 时,调用初始化方法,包括 ThreadLocalMap 的创建 // 以及初始化 value return setInitialValue(); }
-
内存泄漏相关
ThreadLocalMap 中的键值对是用 Entry 存储的,Entry 继承了 WeakReference 代码如下:static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); // 调用父类构造方法,弱引用 value = v;// 强引用 } }
从构造方法中可以看到 entry 对 k 的引用是一个弱引用,对 v 的引用则是强引用。
对 k 使用弱引用的原因是:当 map 外部没有对 ThreadLocal 对象的强引用时,说明这个 ThreadLocal 对象不需要了,k 在下一次 GC 时就会被回收。 如果不采用弱引用,因为某些线程的 map 对 ThreadLocal 对象还有强引用,导致其无法被回收掉。
虽然对 k 使用了弱引用解决了前面提到的内存泄漏的问题,但是有引发了另一个内存泄漏的问题:
如果线程一直在运行,那么该线程持有的 map 就不会被销毁,当外部没有对 ThreadLocal 对象的强引用时,经过一轮 GC,对应的 Entry 就变成了 null -> value 的情况,对 value 的引用是强引用,这就导致 Entry 对象始终无法释放掉。
针对这个问题:在 ThreadLocalMap 的 set() 和 rehash()等方法中,会将 key 为 null 的 Entry 的 value 置为 null,使得 Entry 对象可以被回收。理论上来讲,ThreadLocal 还是可能会出现内存泄漏的,因为最后将 Entry 的 value 置为 null 的操作,不够及时(待确认)。
建议:- 一直持有 ThreadLocal 对象的强引用
- 如果当前线程不需要本地变量时,显示的调用 Threadloca 对象的 remove()方法