简单明了,说说ThreadLocal内存泄漏
本文要讨论的话题是,ThreadLocal内存泄漏问题,首先,明确下我们的讨论范围,先看一个简单的使用ThreadLocal的例子:
注意:存在内存泄漏风险的是我标红的new String("我是大对象")
在调用完testSetGet()方法后后,内存中的引用关系如下图(实线表示强引用,虚线表示弱引用)
其中new ThreadLocal对象被两个引用指向
1.我们自己使用的threadLocal引用,是一个强引用
2.Entry.key引用(Thread对应的ThreadLocalMap的底层数据结构为Entry),是一个弱引用
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