ThreadLocal內存泄漏
內存泄漏:對象不再使用,但是仍然駐留在內存中。
ThreadLocalMap中Entry的結構
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // ThreadLocal變量值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap內部底層是Entry數組,Entry的key爲ThreadLocal,value是線程的該ThreadLocal變量值。Entry內部類繼承了WeakReference類,是弱引用。我們可以看到每個Entry同時包含了一個對key的弱引用和一個對value的強引用。
弱引用的特點是,如果這個對象只被弱引用關聯(沒有任何強引用關聯),那麼對象可以被回收。弱引用不會阻止GC。
正常情況下,當線程終止,會將線程的ThreadLocalMap值設爲null。因爲沒有了強引用,保存在ThreadLocal中的value會被垃圾回收器回收。
強引用鏈
如果強引用一直在,垃圾回收器就不會被回收,因此可能導致內存泄漏。
如果採用線程池,則線程很長時間不會終止,那麼key對應value就不會回收,因爲有下面的調用鏈:
Thread -強引用-> ThreadLocalMap -強引用-> Entry(key爲null)-強引用-> value
Thread和value存在這個強引用鏈路,導致value無法回收,從而導致OOM.
JDK中對此有做處理,在ThreadLocalMap的set、remove、rehas方法內部的resize操作中,有如下語句
if (k == null) { // 過期Entry,清空value
e.value = null; // 利於垃圾回收
然而,如果ThreadLocal不被引用,或者不調用set、remove、rehash方法,那麼調用鏈會一直存在,導致value的內存泄漏。
解決措施
使用完ThreadLocal後,調用remove方法,刪除對應的Entry對象,避免內存泄漏。
// 此時使用完ThreadLocal,回收該ThreadLocal
UserInfoHolder.holder.remove();
線程池不適合使用ThreadLocal。
NPE問題
public class WrongWayNPE {
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
System.out.println(integerThreadLocal.get());
System.out.println(getThreadLocal());
}
static int getThreadLocal() {
return integerThreadLocal.get();
}
}
如代碼中ThreadLocal的泛型類型是Integer類型,未初始化值直接調get()方法,得到的值是null。null無法拆箱裝換成基本類型,引發空指針異常。
因此,我們瞭解NPE問題不是ThreadLocal的問題,而是開發人員代碼問題。