ThreadLocal 為什么會內(nèi)存泄漏?如何解決?
最近,朋友小王在參加某大廠的社招面試,面試官笑瞇瞇地問:“說說ThreadLocal的作用?有啥缺點(diǎn)?”
小王心里一喜,這可是老生常談的問題,于是滔滔不絕地講了一通,啥線程隔離、啥存儲上下文信息、啥用戶Session,統(tǒng)統(tǒng)都擺上了臺面。
面試官聽完點(diǎn)點(diǎn)頭,接著拋出一個靈魂拷問:“那你能分析一下ThreadLocal的內(nèi)存泄漏問題嗎?以及如何避免?”
小王:“呃……這……ThreadLocal還能內(nèi)存泄漏?”
完了!涼涼!
回來后,小王苦哈哈地跟我吐槽,我趕緊給他補(bǔ)了一課。今天,我們就一起來探究:ThreadLocal的內(nèi)存泄漏問題及其解決方案!
ThreadLocal 的底層實現(xiàn)
ThreadLocal是 Java 提供的一種線程封閉機(jī)制,每個線程都可以存儲自己的變量副本,互不干擾。那么問題來了:這些變量存儲在哪里呢?
其實,它們存儲在 Thread 里,每個 Thread 內(nèi)部都有一個 ThreadLocalMap,專門用來存儲 ThreadLocal 變量。
我們來看 Thread 類的源碼(JDK 8):
圖片
嗯,這個 threadLocals 變量就是核心,它的類型是 ThreadLocalMap,專門用來存儲 ThreadLocal 的值。
再看看 ThreadLocalMap 的內(nèi)部結(jié)構(gòu)(簡化版):
圖片
我們發(fā)現(xiàn)了一個關(guān)鍵點(diǎn):Entry 繼承自 WeakReference(弱引用)。這意味著 ThreadLocal 本身是弱引用,但 value 卻是強(qiáng)引用!
那么問題來了!
為什么會內(nèi)存泄漏?
我們來看這樣一段代碼:
圖片
這段代碼有兩個問題:
- threadLocal 被置為 null,但 value 依然存在!
- ThreadLocal 是弱引用,GC 可能會回收它,但 value 依然被 ThreadLocalMap 強(qiáng)引用著!
當(dāng) GC 發(fā)生時:
- ThreadLocal 變量本身會被清理掉,因為它是弱引用。
- ThreadLocalMap 的 Entry.key == null,但 value 還在,占據(jù)大量內(nèi)存!
這樣,如果當(dāng)前線程是線程池的線程,那么這個 value 就一直不會被回收,導(dǎo)致內(nèi)存泄漏!
這,就是 ThreadLocal 內(nèi)存泄漏 的真正原因!
如何避免 ThreadLocal 內(nèi)存泄漏?
既然知道了原因,那解決方案也就呼之欲出了!
方案 1:手動 remove()
最簡單、最有效的方式,就是在使用完 ThreadLocal 變量后,手動調(diào)用 remove() 方法。
圖片
這樣,ThreadLocalMap 里的 Entry 就會被清理掉,value 也就不會泄漏了!
正確示例:
圖片
為什么要用 finally?
因為如果發(fā)生異常,導(dǎo)致 remove() 沒有執(zhí)行,那么 value 還是會泄漏!所以,我們一定要在 finally 代碼塊里手動清理。
方案 2:使用 Static 變量避免多個 ThreadLocal 實例
有時候,我們不希望 ThreadLocal 被 GC 過早回收,可以使用 static 變量 來持有它,確保 ThreadLocal 不會被回收:
圖片
不過,這種方式只適用于 ThreadLocal生命周期和應(yīng)用一致 的情況,否則可能會導(dǎo)致 ThreadLocal 變量不被回收,反而導(dǎo)致 OOM!
方案 3:使用 InheritableThreadLocal
如果是 子線程需要繼承父線程的 ThreadLocal 變量,可以使用 InheritableThreadLocal,避免子線程訪問不到 ThreadLocal 變量:
圖片
但它不能解決內(nèi)存泄漏問題,只是拓展了 ThreadLocal 的作用范圍。
總結(jié)
常見錯誤
- 忘記 remove(),導(dǎo)致 value 無法回收。
- ThreadLocal 被回收,但 value 還在,導(dǎo)致內(nèi)存泄漏。
- 線程池使用 ThreadLocal,但不清理,導(dǎo)致長期占用內(nèi)存。
正確做法
- 在 finally 代碼塊里手動調(diào)用 remove(),避免內(nèi)存泄漏。
- 避免不必要的 ThreadLocal 實例,盡量復(fù)用。
- 如果一定要在線程池中使用 ThreadLocal,務(wù)必 remove() 掉!
尾聲
小王看完這篇文章,恍然大悟:“原來 ThreadLocal 還有這么大的坑,難怪我面試掛了!”
“那你下次再面試,還怕被問到這個問題嗎?”我笑著問。
“怕啥!我還想主動給面試官講一遍,順便聊聊 JVM 內(nèi)存模型!”