最近看ThreadLocal内存泄漏这块,网上说法很多。呼声最多的是因为弱引用导致的内存泄漏。
为啥内存泄漏
1.ThreadLocal什么情况下被回收
ThreadLocal
在ThreadLocalMap
中是以一个弱引用身份被Entry中的Key引用,当GC发生时,ThreadLocal
会不会被回收?
这里就用到引用的知识点了。 在java的四种引用讲过。当一个对象被强引用指向时(这里指可达)。垃圾回收器不会回收他。
例如
ThreadLocal threadlocal1 = new ThreadLocal(); threadlocal1.set("测试");
ThreadLocal
对象此时有两种索引指向的。
- 强引用:
threadlocal1
对应图中【1】 - 弱引用:
Key(WeakReference(threadlocal1))
对应图中的【2】
所以GC发生时,堆内ThreadLocal对象不会被回收。
但是当我们把threadlocal1 =null;
断开强引用时,此时ThreadLoca
l对象只有一个弱引用,那么GC发生时,ThreadLocal
对象被回收了,Entry
变成了一个key为null的Entry
。也叫脏Entry
特点是:
- key为null,value不能被应用程序访问到,因为我们已经没有引用到他的引用了
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
链存在,当前线程迟迟不结束(例如线程池),但不能被使用,成了脏数据,造成了内存泄漏。
看上去好像真是软引用造成的内存泄漏。
2.为啥用弱引用
那换做强引用分析:ThreadLocal
对象被两个强引用指向
- 强引用: threadlocal1
- 强引用: Entry.key
当我们断开程序中的强引用 threadlocal1
时。ThreadLocal
对象仍然被强引用Entry.key
指向,不会回收,这就造成,ThreadLocal
对象与 value
都成为了脏数据。
对比这两种情况:不管软引用还是强引用,都可能出现内存泄漏问题,弱引用反而将内存泄漏的程度降低
利用弱引用的Entry会有key为null这个特征,可以识别哪些是不用的数据,进行清理操作,弱引用 反而提高了ThreadLocal的安全性。
事实上当调用ThreadLocal
的get(),set(),reomve()
方法,都会清除掉线程ThreadLocalMap
中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
3.泄漏的真正原因
所以:ThreadLocal内存泄漏的真正原因:
ThreadLocalMap
的生命周期跟Thread一样长,如果线程池不退,例如线程池,可能造成内存泄漏- 使用完没有及时清理,不再调用
get()、set()、remove()
对脏Entry进行清理
这才是造成内存泄漏的真正原因。
总结:
使用完及时remove,才是最正宗的使用方式。
如有错误,请告知,共同学习。