简单明了,说说ThreadLocal内存泄漏

本文要讨论的话题是,ThreadLocal内存泄漏问题,首先,明确下我们的讨论范围,先看一个简单的使用ThreadLocal的例子:简单明了,说说ThreadLocal内存泄漏

注意:存在内存泄漏风险的是我标红的new String("我是大对象")

简单明了,说说ThreadLocal内存泄漏

在调用完testSetGet()方法后后,内存中的引用关系如下图(实线表示强引用,虚线表示弱引用)

简单明了,说说ThreadLocal内存泄漏

其中new ThreadLocal对象被两个引用指向

1.我们自己使用的threadLocal引用,是一个强引用

2.Entry.key引用(Thread对应的ThreadLocalMap的底层数据结构为Entry),是一个弱引用简单明了,说说ThreadLocal内存泄漏

new String("我是大对象")对象被一个引用指向

1.entry.value引用,是一个强引用

 

好了,现在可以明确我们讨论ThreadLocal内存泄漏的前提了:

       当前线程t没有结束,我们不会再使用threadLocal引用进行set get操作(testAndSet方法结束),并且我们没有显式的调用threadLocal.remove()方法的情况下,new String("我是大对象")这个对象所占内存没有被释放

有几个关键点:

1.当前线程没有结束,结束了new String("我是大对象")自然会销毁,没有讨论的必要

2.我们不会再使用threadLocal引用进行set get操作,说白了就是过了它的生命周期(testAndSet方法结束后),threadLocal这个引用被销毁了

3.我们没有显式的调用threadLocal.remove()方法,和第1点一样,调用了remove就没有讨论的必要了

 

new String("我是大对象")这个对象被回收的条件

new String("我是大对象")这个对象被回收的前提是Entry.v这个强引用失效,下面的条件满足一个它就会失效

1)这个thread终止了,它的栈会被销毁,Entry.v引用被销毁,不满足我们的前提,不讨论

2)显式的将Entry.v置为null,有两种情况:

       2.1)显式调用threadLocal.remove(),不满足我们的前提,不讨论

       2.2)ThreadLocalMap的get set方法中,当发现某个Entry的key指向的是null时,就会将Entry.v置为null

那么,什么情况下Entry.key的指向,也就是new ThreadLocal会变为null呢?

1.它的强引用threadLocal已经被销毁,这个是我们的前提,已经满足

2.发生一次GC,发现new ThreadLocal只被一个弱引用Entry.key指向,将其内存回收

 

那现在我们就知道了,在我们讨论的前提下,new String("我是大对象")想要被回收,要满足如下条件

1.发生过一次GC,new ThreadLocal对象被回收,Entry.key指向null

2.当前线程又调用了一次ThreadLocalMap的set或者get操作

       第1点,有GC为我们保证,肯定可以满足

       第2点,需要当前线程对另外一个ThreadLocal变量进行读写操作,没法保证,这点不满足就会发生内存泄漏

在实际应用中,基本上我们的线程都在线程池中,所以Thread的生命周期很长。ThreadLocal的设计者,就是担心我们在使用ThreadLocal变量时,没有显式的调用remove方法,导致线程在没结束时,ThreadLocalMap中存的对象内存不被释放,所以在ThreadLocalMap的读写方法中都会尽量去检测并释放掉无用内存,但是这些设计都是被动的,我们说了,没法保证第2点,所以我们使用时注意调用remove就好了

 

PS:如果有不足之处,欢迎指出;如果解决了你的疑惑,就点个赞吧o(* ̄︶ ̄*)o