上一篇文章对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 可能存在的内存泄漏。
本人写这篇文章也是再不停的思考、总结,所以你大可不必要求自己一次看懂,多多查看同类型文章,触类旁通,沉淀知识形成自己的知识体系。
驽马十驾功在不舍!