八股|ThreadLocal的内存泄露

ThreadLocal是个好用的工具类,但是使用不好是会导致内存泄露的。

内存泄露:之前开辟使用的内存空间,在使用完毕后未释放,结果导致一直占据该内存单元,无法被gc回收,导致该内存单元后续无法被使用,直到程序结束。

为什么ThreadLocal可能会导致内存泄露问题?
回答这个问题首先需要知道这个问题:
1.ThreadLocal存储value的原理
当我们创建一个ThreadLocal对象时,调用set方法,保存value值,当我们需要使用这个value值的时候,只需要调用get方法即可得到value,那么这个value是保存在ThreadLocal对象之中吗?并不是,通过ThreadLocal存储的value是放在一个叫做ThreadLocalMap对象中的,ThreadLocalMap对象是属于线程对象的,一个线程有一个ThreadLocalMap。
当调用ThreadLocal中的set方法保存value值时,value值实际上是保存在了ThreadLocalMap中,那么当调用的时候该如何去ThreadLocalMap中获得value呢?很简单,就是使用ThreadLocal对象作为key,去ThreadLocalMap中查找,能找到其专属value。
八股|ThreadLocal的内存泄露_第1张图片

那么回到最初的问题,这种存储模式为什么会导致内存泄露呢?
因为如果ThreadLocal对象被gc回收了,消失了,其对应的value并不会随之被回收,而是依旧保存在ThreadLocalMap之中,由于没有了ThreadLocal对象作为key,对应的value是无法被访问到的。那么此时的value就符合“内存泄漏”的定义了——开辟出来的内存后续无法被回收与使用。


ThreadLocal造成内存泄露的典型场景是使用线程池中的线程:当线程池创建一个线程,执行线程的过程中创建了ThreadLocal对象实例,并保存了value。当线程执行完毕之后并没有被销毁,而是存储保存在了线程池之中,此时ThreadLocal对象与value都依旧存在着,当线程从线程池中拿出再次运行,在执行过程中又创建了新的ThreadLocal对象,那么之前的ThreadLocal对象没有强引用指向,被gc回收,之前保存的value无法被访问到,但依旧有ThreadLocalMap的强引用指向,不会被回收,造成了内存泄露。
其实ThreadLocal的设计者已经考虑到了这点,当set和get等指令执行的时候会自动清除key为null的value,不过万事小心为妙,使用完ThreadLocal之后执行remove操作才保险。


在ThreadLocalMap内部,是有一个叫entry[]的数组保存key-value对的,其实如果从存储与取用的角度去考虑,存储value的时候是不需要存储key的,因为value所在的entry,存储在数组的下标是通过key的哈希函数计算出来的,也就是说,给一个ThreadLocal对象,然后通过一个函数算一下,得到一个下标,这个下标就是这个对象所存储的value。不过这样子的话太不保险了,反正也不费事,不如把key也存储在里面,取用的时候可以做一个保险。举个例子说明一下:
entry[] = [ [张三,300元], [李四,50美金], [王五,100卢布] ]
使用张三去检索,找到一个entry [张三,300元],查看这个entry中的key,指向的是张三,正好是检索用的key,无误,可到张三的value是300元。使用张三,得到一个[李四,50美金],检查一个不对,就重新进行检索。


回归最开始的主题,ThreadLocal为什么可能导致内存泄露?答:由于存储的value并不是存在ThreadLocal对象之中,因此ThreadLocal对象被回收后,value可能无法被回收,这样就造成了内存泄露。

你可能感兴趣的:(并发,JVM,jvm,java,算法,开发语言,线程)