Threadlocal-内存泄露问题
在ThreadLocalMap中,用Entry来保存K-V结构数据的。Entry的构造方法已经限定Entry中key只能是ThreadLocal对象。
1 |
|
Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。(问题马上就来了)
四种引用类型
- 强引用(StrongReference)
类似Object obj = new Object()这类似的引用,强引用在程序代码中普遍存在,只要强引用在,垃圾搜集器永远不会搜集被引用的对象。也就是说,宁愿出现内存溢出(OutOfMemoryError),也不会回收这些对象
- 软引用(SoftReference)
在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象
- 弱引用(WeakReference)
弱引用也是用来描述非必需对象的,用java.lang.ref.WeakReference类来表示,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
- 虚引用 (PhantomReference)
java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。
虚引用与软引用和弱引用的区别:虚引用必须和引用队列联合使用。
1
2
3
Object object = new Object();
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收。
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>
的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
如何避免泄漏
为了防止此类情况的出现,我们有两种手段。
使用完线程共享变量后,显示调用
ThreadLocalMap.remove
方法清除线程共享变量;既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的
get()
、set()
方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。JDK建议ThreadLocal定义为
private static
,这样ThreadLocal的弱引用问题则不存在了。