自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ThreadLocal 為什么會內(nèi)存泄漏?如何解決?

開發(fā) 前端
ThreadLocal是 Java 提供的一種線程封閉機(jī)制,每個線程都可以存儲自己的變量副本,互不干擾。那么問題來了:這些變量存儲在哪里呢? 其實,它們存儲在 Thread 里,每個 Thread 內(nèi)部都有一個 ThreadLocalMap,專門用來存儲 ThreadLocal 變量。

最近,朋友小王在參加某大廠的社招面試,面試官笑瞇瞇地問:“說說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)存模型!”

責(zé)任編輯:武曉燕 來源: 軟件求生
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號