驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
ThreadLocal自我总结(二)
/  

ThreadLocal自我总结(二)

开篇

上一篇文章对ThreadLocal的简单入门,了解了使用场景和使用办法。接下来我们继续聊聊:

  • ThreadLocal的内部结构!
  • ThreadLocal中的WeakReference是什么?
  • ThreadLocal是否会触发内存泄露?

纸上得来终觉浅,绝知此事要躬行 。

内部结构

分析一

ThreadLocal的内部结构是什么样子的了?我们先上一张带有结论性的图:

ThreadLocal 图1

这个图我们可以看出几个东西:

  1. 每一个Thread 都包含了一个 ThreadLocalMap
  2. 每一个ThreadLocalMapkeyThreadLocalvalue是 在线程中共享的值。

相关代码细节如下所示:

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 示意图

  • 图2要比图1要复杂点,但更合理些。
  • 图2中左边是栈,右边是堆。栈中保存了2个指向堆的引用:ThreadLocal RefThread Ref
  • ThreadLocalMapKey ThreadLocal,请注意那个红色的虚线,他是WeakReference。这点可以从上述的代码可以看出。

不知道大家有没有考虑过这个地方key 为什么要设置成:WeakReference,大家可以先思考,后面会有解答!

引用的4个类型

Java 中数据类型往大了讲可以分为2个类型:普通类型和引用类型。

普通类型包括:int、byte、short 这些基本类型 因为其结构和内存占用小,所以存放在栈中。

引用类型包括:String、数组、Class等,这些类型通常会比较大,所以通常会在栈中存放指向堆中的引用。

其实细分引用类型还可以分出4类来:

  • 强引用:就是我们平时使用的引用。
  • 软引用:SoftReference,其特性为只有当系统内不能不足的时候, 发生GC才会清理掉。
  • 弱引用:WeakReference ,这个一个很脆弱的引用,当引用的对象仅仅被WeakReference引用的时候,JVM 一旦 GC 他肯定会被 GC 掉。【请注意我加粗的这句话!】
  • 虚引用:VirtualReference,这个引用基本没有使用过。

这里我们谈谈WeakReferenceSoftReference他们的不同。

前者是 GC一旦工作就会被清理,后者却是不一定的。

关于引用软引用和虚引用,我会找个时间在后面补充完善下。

为什么要使用WeakReference

为什么要使用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 可能存在的内存泄漏。

本人写这篇文章也是再不停的思考、总结,所以你大可不必要求自己一次看懂,多多查看同类型文章,触类旁通,沉淀知识形成自己的知识体系。

驽马十驾功在不舍!

骐骥一跃,不能十步。驽马十驾,功在不舍。