ThreadLocal内存泄漏?

本文详细解释了ThreadLocal内存泄露的原理,涉及强引用、弱引用的概念,以及为何在特定情况下ThreadLocal会导致内存泄露。重点讲解了如何通过将ThreadLocal声明为静态变量来防止内存泄漏。
摘要由CSDN通过智能技术生成

ThreadLocal内存泄漏?

在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮性。

强引用与弱引用

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

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

实现原理

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。这些对象之间的引用关系如下,

在这里插入图片描述
实心箭头表示强引用,空心箭头表示弱引用

ThreadLocal 内存泄漏的原因
线程池中,一个线程会不断的从队列中拿去任务执行(不发生异常的话),这个线程是会一直存活的,换句话说他的ThreadLocalMap一直都在。如果此时ThreadLocal作为run方法里的局部变量,且ThreadLocalMap的key不声明为弱引用(则为硬引用)。run方法跑完,threadLocal被回收,但是线程不会结束(因为是线程池的线程),ThreadLocalMap不会被回收,对应上一步的生成的Entry还是保留在ThreadLocalMap的数组中,只要Thread不死(ThreadLocalMap就还在),GC就无法回收那个Entry(key,value),然后key不为null,已经没办法判断是不是回收的了。

还是讨论ThreadLocal作为run方法里的局部变量,且ThreadLocalMap的key声明为弱引用。run方法跑完,threadLocal被回收,但是线程不会结束(因为是线程池的线程),ThreadLocalMap不会被回收,对应上一步的生成的Entry还是保留在ThreadLocalMap的数组中。等待某次GC后,Entry的key被回收,变成Entry(null,value),在remove源码中,有对key==null的Entry进行value=null的help GC的操作。在某次调用remove还能再补救一下,相比为硬引用,已经没办法补救了,已经识别不出来哪些是正在需要GC的了。
总结来说,在ThreadLocal为局部变量的时候,不声明为WeekReference,忘记remove了,然后ThreadLocal被回收后,再也没办法确认ThreadLocalMap这个认为是用完的Entry(key,value)节点是不是可以GC了。而弱引用的Entry的key,某次GC后,无用的Entry都被标记为(null, value),此时就能识别出来是泄露的内存。

为什么建议把ThreadLocal声明为static?
这样就保留了ThreadLocal为强引用了,因为他是GCRoot对象,永不可能回收,key(ThreadLocal对象)是不是弱引用已经无所谓了。因为ThreadLocal是静态变量,所以只存在一个ThreadLocal对象,即使是线程池的线程(假设不发生异常,永远存活),这个ThreadLocal对象也只会占用ThreadLocalMap一个Entry的坑位,换句话说,及时我不回收他,我声明的这个对象在一个线程里只占一个Entry的坑位,永不回收问题也不是很大。对比局部变量ThreadLocal,不回收,每跑一次run方法可能就生成一个Entry,只要不调用remove,内存就会一直泄漏下去。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值