美文网首页
ThreadLocal为什么会导致内存泄漏

ThreadLocal为什么会导致内存泄漏

作者: 和帅_db6a | 来源:发表于2018-11-09 21:01 被阅读0次

    ThreadLocal为什么会导致内存泄漏?

    一个线程对应一块工作内存,线程可以存储多个ThreadLocal。那么假设,开启1万个线程,每个线程创建1万个ThreadLocal,也就是每个线程维护1万个ThreadLocal小内存空间,而且当线程执行结束以后,假设这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出。

    JVM提供解决方案是把ThreadLocal里的Entry设置为弱引用,当垃圾回收的时候,回收ThreadLocal。

    什么是弱引用?

    Key使用强引用:也就是上述说的情况,引用ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。

    Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除

    比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除

    虽然JVM有保障了,但还是有内存泄漏风险。

    ThreadLocalMap使用ThreadLocal对象作为弱引用,当垃圾回收的时候,ThreadLocalMap中Key将会被回收,也就是将Key设置为null的Entry。如果线程迟迟无法结束,也就是ThreadLocal对象将一直不会回收,回顾到上面存在很多线程+TheradLocal,那么也将导致内存泄漏。(内存泄露的重点)

    其实,在ThreadLocal中,当调用remove、get、set方法的时候,会清除为null的弱引用,也就是回收ThreadLocal。

     ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。 

    ThreadLocal提供的方法

    对于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。

    我们先来看一个例子

    运行结果

    很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,可见ThreadLocal不是保证线程安全的

    虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象呢?这个时候ThreadLocal就失效了。仔细观察下图中的代码,你会发现,threadLocal在初始化时返回的都是同一个对象a!

    我们直接看最常用的set操作:

    set 线程局部变量 createMap

    你会看到,set需要首先获得当前线程对象Thread;

    然后取出当前线程对象的成员变量ThreadLocalMap;

    如果ThreadLocalMap存在,那么进行KEY/VALUE设置,KEY就是ThreadLocal;

    如果ThreadLocalMap没有,那么创建一个;

    说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值。

    看一下get操作:

    get

    这里其实揭示了ThreadLocalMap里面的数据存储结构,从上面的代码来看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。

    ThreadLocalMap.Entry:

    弱引用

    在JAVA里面,存在强引用、弱引用、软引用、虚引用。这里主要谈一下强引用和弱引用。

    强引用,就不必说了,类似于:

    A a = new A();

    B b = new B();

    考虑这样的情况:

    C c = new C(b);

    b = null;

    考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?答案是否定的,因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,所以GC不会回收b原先所分配的空间!既不能回收利用,又不能使用,这就造成了内存泄露

    那么如何处理呢?

    可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)

    分析到这里,我们可以得到:

    内存结构图

    这里我们思考一个问题:ThreadLocal使用到了弱引用,是否意味着不会存在内存泄露呢? 

    首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。

    因此,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。

    那么如何有效的避免呢?

    事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

    相关文章

      网友评论

          本文标题:ThreadLocal为什么会导致内存泄漏

          本文链接:https://www.haomeiwen.com/subject/bicaxqtx.html