ThreadLocalMap 中的 key 为什么要设计为 WeakReference?

网上更多是讲 ThreadLocal 的源码和ThreadLocal 出现内存泄漏问题,从ThreadLocalMap中的key为什么设计为弱引用来探讨。
例子:

public class TestDemo {

  private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

  public static void main(String[] args) {

    new Thread(() -> {
      threadLocal.set(1);
      System.out.println(Thread.currentThread().getName() + " :: set 1");
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + " :: get " + threadLocal.get());
    }).start();

    new Thread(() -> {
      System.out.println(Thread.currentThread().getName() + " :: set 2");
      threadLocal.set(2);
      try {
        Thread.sleep(300);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() + " :: get " + threadLocal.get());
    }).start();

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
 }

输出结果:

Thread-0 :: set 1
Thread-1 :: set 2
Thread-1 :: get 2
Thread-0 :: get 1

内存结构如下:
threadlocal.png

假如将key设计为强引用,内存结构如下:
threadlocal2.png

key 设计为强引用和弱引用进行对比,弱引用会被 GC 回收也就是第一张图的虚线部分会断掉,所以每个线程里面ThreadLocalMap 中 key 会为 null,就可能出现 value 的内存泄漏。避免内存泄漏,在使用完需要 remove 操作。(在 ThreadLocal 源码中有 set、get 方法都有调用 replaceStaleEntry 或者 expungeStaleEntry 方法来删除 key 为 null 的 Entry 数据来避免内存泄漏),这里做一个操作将 ThreadLocalRef = null 以及 key 的弱引用都断了,那么堆中 ThreadLocal 将会被回收。

如果 key 设计为强引用,假如将 ThreadLocalRef = null, 以及栈中没有引用指向堆中的 ThreadLocal,那么ThreadLocalMap 无法通过 ThreadLocal 来移除 Entry,就会出现 key 和 value 都会出现内存泄漏

另外,从 Java 内存模型来分析,如下图:
threadlocal3.png

如果 key 为强引用, 将主内存的 ThreadLocal = null,那么工作内存里面的数据还是存在 ThreadLocal 不会被回收。将 key 设计为弱引用也就是为了使全局的 ThreadLocal 为 null 时,ThreadLocal 能够被回收。

总结:
个人认为 ThreadLocalMap 中的 key 设计为弱引用,目的是为了解决 key 和 value 同时出现内存泄漏问题,而value 的内存泄漏问题,JDK 做了一些防护措施,但是为了避免内存泄漏还是使用 remove 操作。

添加新评论