上一篇文章对ThreadLocal的简单入门,了解了使用场景和使用办法。接下来我们继续聊聊:
ThreadLocal的内部结构!ThreadLocal中的WeakReference是什么?ThreadLocal是否会触发内存泄露?纸上得来终觉浅,绝知此事要躬行 。
ThreadLocal的内部结构是什么样子的了?我们先上一张带有结论性的图:

这个图我们可以看出几个东西:
Thread 都包含了一个 ThreadLocalMap。ThreadLocalMap中key是ThreadLocal 而value是 在线程中共享的值。相关代码细节如下所示:
Thread类中有一句关键代码可以证明的我观点1:
public class Thread implements Runnable {
// Thread 代码中有如下一句代码,可以看出每一个 Thread 对象都有一个 ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
}
而 ThreadLocalMap 是一个ThreadLocal中的静态类,其中的 Entry 可以证明我的观点2:
这里为什么要将
ThreadLocalMap设计为static思考中...
public class ThreadLocal<T> {
// 省略其他。。。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
上述分析一看懂后,我们继续来查看分析二。

ThreadLocal Ref和Thread Ref。ThreadLocalMap 中 Key 是 ThreadLocal,请注意那个红色的虚线,他是WeakReference。这点可以从上述的代码可以看出。不知道大家有没有考虑过这个地方key 为什么要设置成:WeakReference,大家可以先思考,后面会有解答!
Java 中数据类型往大了讲可以分为2个类型:普通类型和引用类型。
普通类型包括:int、byte、short 这些基本类型 因为其结构和内存占用小,所以存放在栈中。
引用类型包括:String、数组、Class等,这些类型通常会比较大,所以通常会在栈中存放指向堆中的引用。
其实细分引用类型还可以分出4类来:
SoftReference,其特性为只有当系统内不能不足的时候, 发生GC才会清理掉。WeakReference ,这个一个很脆弱的引用,当引用的对象仅仅被WeakReference引用的时候,JVM 一旦 GC 他肯定会被 GC 掉。【请注意我加粗的这句话!】VirtualReference,这个引用基本没有使用过。这里我们谈谈WeakReference和SoftReference他们的不同。
前者是 GC一旦工作就会被清理,后者却是不一定的。
关于引用软引用和虚引用,我会找个时间在后面补充完善下。
为什么要使用WeakReference?其目的是:为了进一步的防止内存泄漏.
Java的GC是通过可达性性算法来判定哪些对象可以被 GC 的:如果一个对象被引用,那么就不会被 GC 清理。
如果ThreadLocalMap 中的 ThreadLocal 不是WeakReference ,意味着原对象肯定有一层引用在 ThreadLocalMap 中,根据可达性算法 分析那么该对象肯定不会被 GC。
但是一旦使用了 WeakReference 在可达性算法分析的时候就会忽略这个引用,当原对象没有被其他对象引用,仅仅被忽略的 WeakReference 引用的时候,原对象就会被 GC 掉,释放了内存空间,减少了内存泄露的发生可能性。
同时我们需要知道:存在于 ThreadLocal 中的对象因为可能被 GC ,所以不一定可以拿到,所以说不应该将非常重要的数据存在在 ThreadLocal 中。
关于我最后提到的一点::存在于
ThreadLocal中的对象因为可能被GC。我暂时无法通过代码重现,如果你能重现,请不吝赐教。
在上面提到过,每个Thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap.Map中的key为一个ThreadLocal实例。 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向ThreadLocal. 当把ThreadLocal实例置为null以后,没有任何强引用指向ThreadLocal实例,所以ThreadLocal将会被gc回收。
但是,我们的value却不能回收,因为存在一条从Current Thread连接过来的强引用。 只有当前Thread结束以后, Current Thread就不会存在栈中,强引用断开, Current Thread, value将全部被GC回收。
所以得出一个结论就是只要这个线程对象被GC回收,就不会出现内存泄露,但在ThreadLocal设为null和线程结束这段时间之前,该对象不会被回收由此可能存在内存泄露的风险。
本文从内存结构讲到了引用类型的区别,同时提到了ThreadLocal 可能存在的内存泄漏。
本人写这篇文章也是再不停的思考、总结,所以你大可不必要求自己一次看懂,多多查看同类型文章,触类旁通,沉淀知识形成自己的知识体系。
驽马十驾功在不舍!