内存泄露原因找到了,罪魁祸首是Java TheadLocal( 二 )

ThreadLocal相关类的关系总结看了上面的分析是不是对Thread , ThreadLocal , ThreadLocalMap , Entry这几个类之间的关系有点晕了 , 没关系我专门画了一个UML类图来总结(忽略UML标准语法) 。
内存泄露原因找到了,罪魁祸首是Java TheadLocal文章插图

  • 每个线程是一个Thread实例 , 其内部维护一个threadLocals的实例成员 , 其类型是ThreadLocal.ThreadLocalMap 。
  • 通过实例化ThreadLocal实例 , 我们可以对当前运行的线程设置一些线程私有的变量 , 通过调用ThreadLocal的set和get方法存取 。
  • ThreadLocal本身并不是一个容器 , 我们存取的value实际上存储在ThreadLocalMap中 , ThreadLocal只是作为TheadLocalMap的key 。
  • 每个线程实例都对应一个TheadLocalMap实例 , 我们可以在同一个线程里实例化很多个ThreadLocal来存储很多种类型的值 , 这些ThreadLocal实例分别作为key , 对应各自的value , 最终存储在Entry table数组中 。
  • 当调用ThreadLocal的set/get进行赋值/取值操作时 , 首先获取当前线程的ThreadLocalMap实例 , 然后就像操作一个普通的map一样 , 进行put和get 。
ThreadLocal内存模型原理经过上面的分析我们对ThreadLocal相关的类设计已经非常清楚了 , 下面通过一张图更加深入理解一下ThreadLocal的内存存储 。
内存泄露原因找到了,罪魁祸首是Java TheadLocal文章插图
图中左边是栈 , 右边是堆 。 线程的一些局部变量和引用使用的内存属于Stack(栈)区 , 而普通的对象是存储在Heap(堆)区 。
  • 线程运行时 , 我们定义的TheadLocal对象被初始化 , 存储在Heap , 同时线程运行的栈区保存了指向该实例的引用 , 也就是图中的ThreadLocalRef 。
  • 当ThreadLocal的set/get被调用时 , 虚拟机会根据当前线程的引用也就是CurrentThreadRef找到其对应在堆区的实例 , 然后查看其对用的TheadLocalMap实例是否被创建 , 如果没有 , 则创建并初始化 。
  • Map实例化之后 , 也就拿到了该ThreadLocalMap的句柄 , 那么就可以将当前ThreadLocal对象作为key , 进行存取操作 。
  • 图中的虚线 , 表示key对应ThreadLocal实例的引用是个弱引用 。
强引用弱引用的概念ThreadLocalMap的key是一个弱引用类型 , 源代码如下:
static class ThreadLocalMap {// 定义一个Entry类 , key是一个弱引用的ThreadLocal对象// value是任意对象static class Entry extends WeakReference> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = http://kandian.youth.cn/index/v;}}// 省略其他}复制代码下面解释一下常见的几种引用概念 。
强引用一直活着:类似“Object obj=new Object()”这类的引用 , 只要强引用还存在 , 垃圾收集器永远不会回收掉被引用的对象实例 。
弱引用回收就会死亡:被弱引用关联的对象实例只能生存到下一次垃圾收集发生之前 。 当垃圾收集器工作时 , 无论当前内存是否足够 , 都会回收掉只被弱引用关联的对象实例 。 在JDK 1.2之后 , 提供了WeakReference类来实现弱引用 。
软引用有一次活的机会:软引用关联着的对象 , 在系统将要发生内存溢出异常之前 , 将会把这些对象实例列进回收范围之中进行第二次回收 。 如果这次回收还没有足够的内存 , 才会抛出内存溢出异常 。 在JDK 1.2之后 , 提供了SoftReference类来实现软引用 。
虚引用也称为幽灵引用或者幻影引用 , 它是最弱的一种引用关系 。 一个对象实例是否有虚引用的存在 , 完全不会对其生存时间构成影响 , 也无法通过虚引用来取得一个对象实例 。 为一个对象设置虚引用关联的唯一目的就是能在这个对象实例被收集器回收时收到一个系统通知 。 在JDK 1.2之后 , 提供了PhantomReference类来实现虚引用 。
内存泄露是不是弱引用的锅?从表面上看内存泄漏的根源在于使用了弱引用 , 但是另一个问题也同样值得思考:为什么ThreadLocalMap使用弱引用而不是强引用?
翻看官网文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. 为了处理非常大和长期的用途 , 哈希表条目使用weakreference作为键 。
分两种情况讨论:
(1)key 使用强引用
引用ThreadLocal的对象被回收了 , 但是ThreadLocalMap还持有ThreadLocal的强引用 , 如果没有手动删除 , ThreadLocal不会被回收 , 导致Entry内存泄漏 。
(2)key 使用弱音
引用ThreadLocal的对象被回收了 , 由于ThreadLocalMap持有ThreadLocal的弱引用 , 即使没有手动删除 , ThreadLocal也会被回收 。 value在下一次ThreadLocalMap调用set、get、remove的时候会被清除 。