ThreadLocal --弱引用和内存泄漏
内存泄露 (memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,即无法被垃圾回收器回收。
内存溢出 (out of memory),是指程序在申请内存时,没有足够的内存空间供其使用(杯子就只能装500ml的水,都满了,你还想加水),出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。你申请了一个盘子用尽各种方法只能装4个果子,你却装了5个,结果多的那个掉到地上不能吃了。这就是溢出!比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出.
内存泄露 (memory leak)会最终会导致内存溢出 (out of memory)。
弱引用相关概念
在传统的引用定义下,一个对象只有“被引用”或者“未被引用”两种状态。我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象。于是将引用分为强引用、软引用、弱引用和虚引用4种,这4种引用强度依次逐渐减弱。
强引用:是指引用赋值,好比Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
弱引用:描述那些非必须的对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止,无论当前内存是否足够,都会被回收。
《深入理解Java虚拟机》中介绍


但是因为threadLocalMap的Entry强引用了threadLocal,造成threadLocal无法被回收。
在没有手动删除这个Entry以及CurrentThread依然运行的前提下,始终有强引用链 threadRef->currentThread->threadLocalMap->entry,Entry就不会被回收(Entry中包括了ThreadLocal实例和value),导致Entry内存泄漏。也就是说,ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的。
如果key使用弱引用
此时ThreadLocal的内存图(实线表示强引用,虚线表示弱引用)如下:
由于ThreadLocalMap只持有ThreadLocal的弱引用,没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收,此时Entry中的key=null。
但是在没有手动删除这个Entry以及CurrentThread依然运行的前提下,也存在有强引用链 threadRef->currentThread->threadLocalMap->entry -> value ,value不会被回收, 而这块value永远不会被访问到了,导致value内存泄漏。
也就是说,ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。
出现内存泄漏的真实原因?
在以上两种内存泄漏的情况中,都有两个前提:
-
没有手动删除这个Entry。
-
CurrentThread依然运行。
第一点很好理解,只要在使用完ThreadLocal,调用其remove方法删除对应的Entry,就能避免内存泄漏。
第二点稍微复杂一点, 由于ThreadLocalMap是Thread的一个属性,被当前线程所引用,所以它的生命周期跟Thread一样长。那么在使用完ThreadLocal的使用,如果当前Thread也随之执行结束,ThreadLocalMap自然也会被gc回收,从根源上避免了内存泄漏。
综上,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。
为什么使用弱引用?
要避免内存泄漏有两种方式:
-
使用完ThreadLocal,调用其remove方法删除对应的Entry。
-
使用完ThreadLocal,当前Thread也随之运行结束。
相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的。
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。
这就意味着使用完ThreadLocal,CurrentThread依然运行的前提下,就算忘记调用remove方法,弱引用比强引用可以多一层保障:弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏。

浙公网安备 33010602011771号