zoukankan      html  css  js  c++  java
  • 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后执行某些操作

    转载请注明出处:【Hellxz】 https://www.cnblogs.com/hellxz/p/java-threadlocal.html

  • 相关阅读:
    【已解决】github中git push origin master出错:error: failed to push some refs to
    好记心不如烂笔头,ssh登录 The authenticity of host 192.168.0.xxx can't be established. 的问题
    THINKPHP 5.0目录结构
    thinkphp5.0入口文件
    thinkphp5.0 生命周期
    thinkphp5.0 架构
    Django template
    Django queryset
    Django model
    Python unittest
  • 原文地址:https://www.cnblogs.com/hellxz/p/java-threadlocal.html
Copyright © 2011-2022 走看看