ThreadLocal的内存泄漏

  1. 什么是内存泄漏

    1. 程序在申请内存后,无法释放已申请的内存空间
    2. 在定义变量时,需要一段内存空间来存储数据信息,而这段内存如果一直不被释放,那么就会导致内存被占用光,而被占用的这个对象,一直不能被回收掉,这就是内存泄漏
  2. ThreadLocal

    java 复制代码
    private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        @SuppressWarnings("unchecked")
                        ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }
    1. 每一个ThreadLocal维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本
  3. 强引用

    1. 使用最普遍的引用,一个对象具有强引用,不会被垃圾回收器回收,当内存空间不足,JAVA虚拟机宁愿抛出OOM错误,使程序异常终止,也不会受这种对象
    2. 如果想取消强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象
  4. 弱引用

    1. JVM进行垃圾回收时,无论是内存是否充足,都会回收被弱引用关联的对象,在java中,用java.lang.ref.WeakReference类来表示,可以在缓存中使用弱引用
  5. 内存泄漏

    1. ThreadLocalMap使用ThreadLocal的弱应用作为key,如果一个ThreadLocal不存在外部强引用,Key势必会被GC回收,这样就导致ThreadLocal中key为null,而value还存在着强引用,只有Thread线程退出以后,value的强引用链条才会断掉
    2. 如果当前线程迟迟不结束,这些key为null的Entry的value就会一直存在一条强引用链
    3. Thread Ref -> Thread -> ThreadLocalMap ->Entry -> value
    4. 这个时候,永远无法回收,就会造成ThreadLocal出现内存泄漏的问题
  6. 如果ThreadLocalMap使用ThreadLocal的强引用

    1. 因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
    2. 当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收,当key为null,在下一次ThreadLocalMap调用set和get,remove方法时会被清除value的值
  7. 为什么使用弱引用

    1. 因为使用弱引用可以多一层保障,弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove时会被清除

    2. 因此,ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应的key就会导致内存泄漏,而不是因为弱引用

    java 复制代码
    static class ThreadLocalMap {
        //hreadLocalMap中数据是存储在Entry类型数组的table中的,Entry继承了WeakReference(弱引用)
        static class Entry extends WeakReference<ThreadLocal<?>> {
                Object value;
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
    **成员变量**
    //初始容量 
    private static final int INITIAL_CAPACITY = 16;
    
    //ThreadLocalMap数据真正存储在table中
    private Entry[] table;
    
    //ThreadLocalMap条数        
    private int size = 0;
    
    //达到这个大小,则扩容       
    private int threshold; 
    1. 构造函数

      java 复制代码
      ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
              //初始化table数组,INITIAL_CAPACITY默认值为16
              table = new Entry[INITIAL_CAPACITY];
              //key和16取得哈希值
              int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
              //创建节点,设置key-value
              table[i] = new Entry(firstKey, firstValue);
              size = 1;
              //设置扩容阈值
              setThreshold(INITIAL_CAPACITY);
      }
    2. remove方法

      java 复制代码
      private void remove(ThreadLocal<?> key) {
              Entry[] tab = table;
              int len = tab.length;
              int i = key.threadLocalHashCode & (len-1);
              //如果threadLocalHashCode计算出的下标找到的key和传入key不同,则证明出现哈希冲突,则循环向下查找
              for (Entry e = tab[i];
                   e != null;
                   e = tab[i = nextIndex(i, len)]) {
                  //如果key相同
                  if (e.get() == key) {
                      //删除当前Entry
                      e.clear();
                      //清理
                      expungeStaleEntry(i);
                      return;
                  }
              }
          }
  8. 解决方法

    1. 每次使用完ThreadLocal都调用他的remove方法清除数据
    2. 将ThreadLocal变量定义为private static,这样就一直存在ThreadLocal的强引用,也就是保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉
相关推荐
架构文摘JGWZ2 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师3 小时前
spring获取当前request
java·后端·spring
aPurpleBerry3 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏3 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko3 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring
2301_789985943 小时前
Java语言程序设计基础篇_编程练习题*18.29(某个目录下的文件数目)
java·开发语言·学习
IT学长编程3 小时前
计算机毕业设计 教师科研信息管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·毕业设计·springboot·毕业论文·计算机毕业设计选题·计算机毕业设计开题报告·教师科研管理系统
m0_571957583 小时前
Java | Leetcode Java题解之第406题根据身高重建队列
java·leetcode·题解
程序猿小D4 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa
Zd085 小时前
14.其他流(下篇)
java·前端·数据库