ThreadLocal内存泄露问题

了解ThreadLocal内存泄露问题首先要了解 1.ThreadLocal是什么?2ThreadLocal.有什么用?3.java的四种引用类型,然后你就理解了ThreadLocal为什么会发生内存泄露问题了。

1. ThreadLocal是什么?

​ ThreadLocal,即 本地线程变量 ,是一个以ThreadLocal对象为键(key)、任意对象为值(value)的存储结构。每个线程Thread拥有一份自己的本地线程变量 ,多个线程互不干扰。

1.1 ThreadLocal有什么用?

  • 如果你创建了一个ThreadLocal变量,那么访问这个变量的 每个线程 都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get()set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
  • ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

1.2 ThreadLocal源码分析

Thread类部分源码:

  • Thread 类中有一个 ThreadLocalMap 类型的变量:threadLocals

  • ThreadLocalMap 底层数据结构是由一个Entry[]数组组成,Entry对象用来保存key/value, 它的 key 是 ThreadLocal<?> k ,继承自WeakReference(弱引用类型,发生GC时会被回收)

    public class Thread implements Runnable {
      
        省略...
          
        //与此线程有关的ThreadLocal值。由ThreadLocal类维护
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    

ThreadLocal类部分源码:

  • 通过 ThreadLocal 对象调用set()方法的源码会发现,实际上是获取当前线程后拿到当前线程的threadLocals成员变量

  • 每个线程往 ThreadLocal 里 set 值的时候,都会往自己的ThreadLocalMap里存,读也是以 ThreadLocal 作为引用,在自己当前线程的 ThreadLocalMap 里找对应的 key ,从而实现了 线程隔离

    public class ThreadLocal<T> {
      ...
      public void set(T value) {
          Thread t = Thread.currentThread();
          //取出 Thread 类内部的 threadLocals 变量(哈希表结构)
          ThreadLocalMap map = getMap(t);
          if (map != null)
              map.set(this, value);
          else
              createMap(t, value);
      }
      
      ThreadLocalMap getMap(Thread t) {
          return t.threadLocals;
      } 
      
    }
    

1.3 了解Java的四种引用类型

  • 强引用: new出来的对象就是强引用类型,只要强引用存在 GC将永远不会回收被引用的对象,哪怕内存不足的时候
  • 软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
  • 弱引用:使用WeakReference 修饰的对象被称为弱引用,弱引用指向的对象只要发生 GC 就会被回收
  • 虚引用:使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知

2. ThreadLocal造成内存泄露的原因

​ ThreadLocal 中有个静态内部类是 ThreadLocalMap ,它里面定义了 Entry 对象来保存数据且继承的是弱引用WeakReference。在 Entry 内部使用 ThreadLocal 作为 key ,使用我们设置的值作为 value 。(在构造方法中,将key设置成了弱引用)。如果ThreadLocal是null了,也就是要被GC回收了,但是此时我们的ThreadLocalMap(thread 的内部属性)生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

public class ThreadLocal<T> {  
...
    static class ThreadLocalMap {

        /**
         * 构造Entry对象时,将key(ThreadLocal)对象设置为了弱引用
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
		...
	}
  ...
  }

2.1 为什么ThreadLocal的key要使用弱引用?

​ 🐸 ThreadLocalMap 是线程的成员变量,只要线程还未结束,则会一直被Thread强引用着,假设 Entry 对象的 key 是对 ThreadLocal 对象的强引用,如果在其他地方都没有对这个 ThreadLocla 对象的引用了,然后在使用ThreadLocalMap的过程中又没有正确地在用完后就调用 remove 方法,所以这个 ThreadLocal 对象和所关联的 value 对象就会跟随着线程一直存在,这样可能会造成OOM。

2.2 为什么ThreadLocal的value不能使用弱引用?

​ 🐸【假设Entry 的 value 是弱引用】:假设 key 所引用的 ThreadLocal 对象还被其他的引用对象强引用着,那么这个 ThreadLocal 对象就不会被 GC 回收,但如果 value 是弱引用且不被其他引用对象引用着,那 GC 的时候就被回收掉了,那线程通过 ThreadLocal 来获取 value 的时候就会获得 null,对我们来说,value 才是我们想要保存的数据,ThreadLcoal 只是用来关联 value 的,如果 value 都没了,还要 ThreadLocal 干嘛呢?所以 value 不能是弱引用


☘️ 那么问题来了,我们了解了ThreadLocalkey会过期因而会造成内存泄露问题,那么ThreadLocal又是如何处理这些过期Key的呢?且看:ThreadLocalMap.key到期之探测是清理+启发式清理流程 - lihewei - 博客园 (cnblogs.com)

posted @ 2023-03-14 10:58  lihewei  阅读(703)  评论(0编辑  收藏  举报
-->