【内存泄漏】测试ThreadLocal--gc后引发的threadLocalMap的key为null,但value不为null的情况

效果

发生gc后,key为null,value不为null。
注意:这里立即释放了对threadLocal实例的强引用,帮助gc回收查看弱引用的使用方法

在这里插入图片描述

原因

ThreadLocal#set后会将threadLocal实例本身作为key 放入 Thread.currentThread().threadLocalMap中,与threadlocal#set的value构成一对Entry。而Entry使用了threadLocal的实例作为 弱引用。因此当发生gc的时候,弱引用的key会被回收掉,而作为强引用的value还存在。

作为key的弱引用的ThreadLocal
在这里插入图片描述
这里借用网图帮助理解
在这里插入图片描述

题外话

如果没有失去对ThreadLocal本身的强引用,那么不会回收threadLocal。(注释中有标注helpGC的地方)
而我们平时代码中,常常使用static final修饰threadLocal保留一个全局的threadLocal方便传递其他value(threadLocal一直被强引用),作为key的threadLocal就不会被回收,更不会导致key为null。

public static final ThreadLocal<Object> THREAD_LOCAL =  new ThreadLocal<>();

使用ThreadLocal关键之处还是在于:使用完毕要记得remove。特别是在线程池中使用的时候。(否则会等到下一次set的时候才替换掉value–>set的key为同一个threadLocal对象,所以是替换)

代码

threadLocal被强引用 引用,无法被回收
在这里插入图片描述

/**
 * 测试ThreadLocal 在gc后引发的threadLocalMap的key为null,但value不为null的情况
 * @Author thewindkee
 * @Date 2019/12/27 9:28
 */
public class Test {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        Thread t = new Thread(()->test("abc",false));
        t.start();
        t.join();
        System.out.println("--gc后--");
        Thread t2 = new Thread(() -> test("def", true));
        t2.start();
        t2.join();
    }

    private static void test(String s,boolean isGC)  {
        try {
            ThreadLocal<Object> threadLocal = new ThreadLocal<>();
            threadLocal.set(s);
            threadLocal = null;//失去对threadLocal的强引用 ,help gc
            if (isGC) {
                System.gc();
            }
            Thread t = Thread.currentThread();
            Class<? extends Thread> clz = t.getClass();
            Field field = clz.getDeclaredField("threadLocals");
            field.setAccessible(true);
            Object threadLocalMap = field.get(t);
            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) {
                    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)));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

本文首次发布于2019年12月27日

强引用、软引用、弱引用、虚引用测试
ThreadLocal为什么会内存泄漏

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值