栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal内存泄漏的真正原因

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal内存泄漏的真正原因

注意:
看这篇文章之前得对 ThreadLocal 有个大致的了解,不然看起来还是蛮吃力的。

一、内存泄漏是因为弱引用吗?

先说结果,内存泄漏的确是因为弱引用引起的,为什么呢?

先看下 ThreadLocal 的 set 方法:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); 
}

createMap 如下:

void createMap(Thread t, T firstValue) {
    // t 指的是当前线程,即调用 threadLocal.set方法所在的线程
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

可以看出ThreadLocal 的数据是存在 ThreadLocalMap,而 ThreadLocalMap 是线程Thread中的变量,所以可以做到线程的数据隔离。

ThreadLocalMap 中是以数组的形式存放数据 private Entry[] table,至于插入数据时产生的碰撞这里不作说明了,可能下一篇会说明。Entry 的数据结构如下:

static class Entry extends WeakReference> {
    
    Object value;
    Entry(ThreadLocal k, Object v) {
        super(k); // key 为 ThreadLocal的弱引用
        value = v;
    }
}

我们把上面的引用链画下来,如下:

其中,虚线表示弱引用。

下面来说下为什么弱引用会导致内存泄漏?

一般项目中使用线程都是利用线程池,而线程池中的线程可能一直存活,这就导致了当外部不再持有 ThreadLocal的引用的时候,发生GC,导致 ThreadLocal 对象被回收了。

当 Entry 中的 key 即 ThreadLocal 对象被回收了之后,会发生 Entry 中 key 为 null 的情况,其实这个 Entry 就已经没用了,但是又无法被回收,因为有 Thread->ThreadLocalMap ->Entry 这条强引用在,这样没用的内存无法被回收就是内存泄露。

当然,如果不是线程池使用方式的话,其实不用关系内存泄漏,反正线程执行完了就都回收了,但是一般我们都是使用线程池。

但是,没关系,在作者设计 ThreadLocal 的时候已经考虑到这种情况了,所以在 ThreadLocal 的 set、get、以及扩容的时候都会清理 key = null 的数据。但是最佳实践是当不再使用的时候,手动 remove 掉,如下:

void dosth {
 threadlocal.set("1234");
 try {
  // do sth ... 
 } finally {
  threadlocal.remove();
 }
}
既然弱引用会导致内存泄漏,那为什么不用强引用呢?

同样的,如果是强引用,如果线程一直在,那么就算外部不再持有 ThreadLocal 的引用,但是会存在如下的引用链 GCRoots -> 线程对象 -> ThreadLocalMap -> Entry -> Key(ThreadLocal),导致ThreadLocal 对象得不到回收。

看到这里,可能有人会说那线程被回收之后就好了呀。

重点来了!线程在我们应用中,常常是以线程池的方式来使用的,而线程池中的线程一般是不会被清理掉的,所以这个引用链就会一直在,那么 ThreadLocal 对象即使没有用了,也会随着线程的存在,而一直存在着!

可以通过如下的方法来看看弱引用的 ThreadLocal 是否被回收。

ThreadLocal threadLocal = new ThreadLocal<>();
        threadLocal.set("我是在主线程中设置的值");
        // 如果不设置为null,那么由于有强引用持有 threadLocal,那么不会被回收
        threadLocal = null; // 断开 ThreadLocal 的强引用
        System.gc(); // 主动垃圾回收

        Thread curThread = Thread.currentThread();
        Class clz = curThread.getClass();
        Field field = null;
        try {
            field = clz.getDeclaredField("threadLocals");
            field.setAccessible(true);
            Object threadLocalMap = field.get(curThread);

            Class tlmClass = threadLocalMap.getClass();
            Field tableField = tlmClass.getDeclaredField("table");
            tableField.setAccessible(true);
            Object[] arr = (Object[]) tableField.get(threadLocalMap);

            for (Object o : arr) {
                if (o == null) continue;
                Class entryClass = o.getClass();
                Field valueField = entryClass.getDeclaredField("value");
                Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
                valueField.setAccessible(true);
                referenceField.setAccessible(true);
                System.out.println(String.format("弱引用key:%s    值:%s", referenceField.get(o), valueField.get(o)) + " , thread >> " +Thread.currentThread().getName());
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/703676.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号