• 【转载】ThreadLocal与引用类型相关知识点


    0 写在前边

    今天以 “TheadLocal 为什么会导致内存泄漏” 为题与朋友们讨论了一波,引出了一些原理性的内容,本文就这个问题作答,并扩展相关的知识点

    1 ThreadLocal 和 ThreadLocalMap 是什么?

    简单来说,ThreadLocal 是一种操作与线程绑定的共享对象的工具,通过ThreadLocal可以将一些对象保存在线程上,实现同线程不同方法之间的对象共享。

    线程的上下文由 ThreadLocalMap 组成,它是 ThreadLocal 的静态内部类,存储着线程共享对象。

    一般来说,我们无需显式创建ThreadLocalMap,也无需为装入ThreadLocalMap 对象设 key 值,因为在 set 方法执行时会创建 ThreadLocalMap,并将当前 ThreadLocal 对象作为 key,待存储对象作为 value,存储到 ThreadLocalMap。

    值得一提的是,ThreadLocalMap 的 key 与 value 的类型是不同的,key 是弱引用类型的,value 是强引用类型的。

    2 Thread、ThreadLocal 与 ThreadLocalMap 之间的关系

    Thread 与 ThreadLocalMap

    首先 ThreadLocalMap 是与 Thread 进行绑定的,ThreadLocalMap 是线程上实际存储共享对象的容器。

    如下图,threadLocals 就是默认的 ThreadLocalMap,默认为 null

    绑定 ThreadLocalMap 到 Thread 的位置在 ThreadLocal 的 createMap 方法中,threadLocals 引用指向 ThreadLocalMap。(这里还包含了放置第一个对象的操作)

    ThreadLocal 的 getMap 方法取的就是线程的 threadLocals

    ThreadLocal与ThreadLocalMap

    ThreadLocalMap 是 ThreadLocal 类的静态内部类,ThreadLocal 是操作 ThreadLocalMap 的工具,还是 ThreadLocalMap 的 key 对象,在 ThreadLocal 作为 key 保存前转换成弱引用类型。

    一般我们通过 ThreadLocal 的 set 方法进行保存对象,在 set 方法内部获取了当前线程的 ThreadLocalMap,调用 ThreadLocalMap 的 set 方法进行保存对象。

    使用 this 关健字将当前使用的 ThreadLocal 对象作为 key 存到 ThreadLocalMap 中,以减小 key 冲突的可能性。

    ThreadLocalMap 中的 set 方法主要是创建一个 Entry 对象放进数组中,Entry 继承 WeakReference 类,将 Entry 的 key(也就是 ThreadLocal)转成弱类型。

    一句话总结它们之间的关系

    每个 Thread 绑定 ThreadLocalMap 来存储线程上下文共享对象,ThreadLocalMap 中的key(即,ThreadLocal)在同一线程中是唯一的。单线程情况下,每个 ThreadLocal 只对应一个值对象。

    3 ThreadLocal导致的内存泄漏的原因是什么?

    导致内存泄漏的原因在于程序员未在使用完ThreadLocalMap中存储的对象后清除这些对象。

    ThreadLocalMap是维护在Thread内部的,意味着只要线程不退出,ThreadLocalMap中保存的对象引用就会一直存在,由于垃圾回收器是依据可达性分析的,存在强引用的对象不会被回收,而ThreadLocalMap中存储的对象都是强引用的。

    假设当前线程处于一个死循环中(比如,Tomcat),随着ThreadLocalMap保存的对象越来越多,垃圾收集器无法回收强引用的对象,就会导致可用堆内存越来越小,出现内存泄漏,最终抛出OOM。

    4 如何清理 ThreadLocalMap 存储的对象?

    用完 ThreadLocal 存储的对象后,只需调用 ThreadLocal 的 remove 方法,就会自动将 ThreadLocalMap 中的 K-V 对引用置空,垃圾收集器会在合适的时机内清除 K-V 对象释放内存。

    ThreadLocal 类 remove 方法,获取当前线程上的 ThreadLocalMap 移除以此 ThreadLocal 为 key 的对象。通过调用 ThreadLocalMap 的 remove 方法实现。

    ThreadLocalMap 的 remove 方法中,e.clear() 调用的是key对象继承的 Reference 类的 clear(),对 key 引用置空,expungeStaleEntry(i) 对 value 引用置空。

    ThreadLocalMap 的 expungeStaleEntry 方法,分别取出 ThreadLocalMap 中的 Entry 的 value 与 Entry 本身先后置空。

    5 为什么ThreadLocalMap使用弱引用key?

    ThreadLocalMap 是与线程绑定的,线程不退出,强引用的key对象就不会被垃圾回收,当用户妥善处理的无用K-V对象就会导致内存泄漏。利用弱引用可以及时被 GC 的特性,回收绝大多数key(除 static 域的全局 key 外),以减缓内存泄漏。

    实际上最需要回收的是value对象,弱引用key只是一种挽救措施。

    6 ThreadLocalMap 为什么使用强引用 value,而不是弱引用?

    与 key 不同的是,key 仅作为索引,实际工作的是 value,value 需要共享。

    当局部 value 对象所在的方法结束,栈桢被清空时,会将局部 value 对象引用销毁,垃圾收集器会清除没有引用的对象。

    如果此时设置成弱引用装入 Map,value 对象会在某次 GC 时消亡,这肯定不是我们希望的。

    我们希望的是value对象可以维持存活以共享,只有强引用可以达到目的。

    7 线程池会累积 ThreadLocalMap 的占用的内存而出现内存泄漏吗?

    解释下问题,之前有讲过,ThreadLocalMap 与 Thread 的生命周期是一致的,而线程池技术是复用线程的,如果之前的 ThreadLocalMap 已经开始内存泄漏,是否会出现累积已泄漏的内存?

    线程池不存在这个问题,虽然它复用了线程,但是清除了上一线程的所有资源。

    8 线程有一个ThreadLocalMap,ThreadLocal也只有一个值,为何还会内存泄漏?

    这是我自己思考时提出来的,能问出这个问题,只能说当时还没完全理解ThreadLocal与ThreadLocalMap的对应关系。

    原问题:一个线程有一个ThreadLocalMap(不考虑继承ThreadLocal的那个实现),即然 ThreadLocal 作为 key 了,那么ThreadLocalMap中是否只会有一个Entry,内存再泄露能泄露到哪里去?(误认为ThreadLocalMap与ThreadLocal绑定,只有一个,也只能装一个Entry,这是错误的)

    其实 ThreadLocal 我们可以创建很多个,ThreadLocalMap却只有一个(不考虑继承ThreadLocal的那个实现),通过创建多个 ThreadLocal 来存取 ThreadLocalMap 中的对象。

    伪代码举例:

    ThreadLocal<A> aThreadLocal = new ThreadLocal<A>();
    ThreadLocal<B> bThreadLocal = new ThreadLocal<B>();
    aThreadLocal.set(new A("a"));
    bThreadLocal.set(new B("b"));
    aThreadLocal.get();
    bThreadLocal.get();
    

    我在ThreadLocal的getMap()打了断点,当前线程中 ThreadLocalMap 中有两个对象,可以看到referent中记录了保存对象的ThreadLocal对象的HashCode。这起码证明了ThreadLocalMap不仅仅能装一个对象

    9 【扩展】Java对象的引用类型

    • 强引用:常见new的对象,只要还有强引用的对象,则不会被GC
    • 软引用:比强引用弱,仅当JVM内存不足时才会清理,清理时机在OOM前
    • 弱引用:只提供非强制的映射关系,会被JVM择机清理
    • 虚引用(幻象引用):无法通过它访问对象,只确保对象在finalize后执行某些操作
  • 相关阅读:
    [LeetCode] 216. 组合总和 III
    [LeetCode] 215. 数组中的第K个最大元素
    [LeetCode] 215. 数组中的第K个最大元素
    [LeetCode] 215. 数组中的第K个最大元素
    [LeetCode] 213. 打家劫舍 II
    [LeetCode] 212. 单词搜索 II
    [LeetCode] 211. 添加与搜索单词
    转:十大编程算法助程序员走上高手之路
    推荐用于格式化以及高亮显示SQL文的PHP类-SqlFormatter
    转:实用 .htaccess 用法大全
  • 原文地址:https://www.cnblogs.com/gloryhope/p/12735957.html
Copyright © 2020-2023  润新知