ThreadLocal的內(nèi)存泄露問題
ThreadLocal的內(nèi)部實(shí)現(xiàn)
在每一個(gè)線程Thread對象中,都維護(hù)了一個(gè)ThreadLocalMap對象。
ThreadLocalMap中又維護(hù)了一個(gè)k v 形式的Entry對象,key指向了當(dāng)前ThreadLocal對象,value就是我們實(shí)際在ThreadLocal中存儲(chǔ)的值。
注意,這里的Entry中的key存放是ThreadLocal的弱引用。
實(shí)現(xiàn)指的是強(qiáng)引用,虛線指的是弱引用。
其實(shí)際上,ThreaLocal本身是不存儲(chǔ)值的,我們在使用其對應(yīng)的set、get方法時(shí),都是操作的其對應(yīng)的ThreadLocalMap對象。
為什么會(huì)出現(xiàn)內(nèi)存泄露?
從上述可以看到,在Entry中的key存儲(chǔ)的ThreadLocal的弱引用。
弱引用在發(fā)生GC時(shí),就會(huì)被垃圾回收掉,具體可以參考JVM相關(guān)的知識(shí)。
所以,在當(dāng)前線程正在運(yùn)行的時(shí)候,發(fā)生GC時(shí),在ThreadLocal對象沒有被其它地方強(qiáng)引用時(shí),key指向ThreadLocal的虛引用就會(huì)立即斷開(被垃圾回收掉),這時(shí),就會(huì)出現(xiàn)ThreadLocalMap中存在key為null的Entry,只要當(dāng)前線程不結(jié)束,該ThreadLocalMap對象就會(huì)一直存在,永遠(yuǎn)無法回收,因?yàn)榇藭r(shí)還存在一條強(qiáng)引用的鏈路,從圖中也可以發(fā)現(xiàn):
Current Thread Reference --> Current Thread --> ThreadLocalMap --> EntryValue --> Object
所以這個(gè)時(shí)候就造成了內(nèi)存泄露。
Entry對象的key為什么要使用弱引用,有什么好處?
在上述所說的問題中,即使ThreadLocalMap中存在key為null的Entry,但是該Entry的value值并不會(huì)因?yàn)镚C而被回收(value存本身就存著一個(gè)強(qiáng)引用的對象),所以就導(dǎo)致了該對象不會(huì)被回收掉而出現(xiàn)了內(nèi)存泄露。
其實(shí),ThreadLocalMap在設(shè)計(jì)時(shí)就考慮到了這個(gè)方面,它也采取了一些措施來避免這種key為null,而value不為null的對象占用內(nèi)存,在我們調(diào)用ThreadLocal的set、get、remove方法時(shí),都會(huì)將這些key為null的對象清空掉,避免因?yàn)檫@種情況而導(dǎo)致內(nèi)存泄露。
這也就是為什么key要存儲(chǔ)弱引用的原因。
假設(shè)如果存儲(chǔ)的強(qiáng)引用,我們斷開ThreadLocal Reference —> ThreadLocal的引用,會(huì)發(fā)現(xiàn)key強(qiáng)引用了ThreadLocal,導(dǎo)致該對象永遠(yuǎn)無法被GC。
但是,即使上述提供了避免內(nèi)存泄露的措施,但是不能完全避免,比如以下的情況:
- 分配了ThreadLocal對象,但是并沒有執(zhí)行其get、set、remove方法,導(dǎo)致不能有效的清除null對象;
- 使用線程池的情況下,使用完ThreadLocal一定要使用remove方法即時(shí)清理,因?yàn)門hreadLocal是屬于某個(gè)線程的,而在使用線程池的情況下,這些線程都是可重復(fù)利用、存活時(shí)間長的線程,如果在使用過程中不僅從即使的remove,那么不僅會(huì)造成內(nèi)存泄露的問題,還會(huì)引發(fā)一些功能邏輯問題,比如,B請求可能和A請求分配到了線程池中的同一個(gè)線程,那么它們拿到的ThreadLocal就是一樣的。
set
:
cleanSomeSlots
:
get
:
關(guān)于弱引用的一些知識(shí)補(bǔ)充
學(xué)習(xí)的過程中想到了一個(gè)問題,弱引用會(huì)不會(huì)導(dǎo)致運(yùn)行過程中GC清除key,導(dǎo)致找不到對應(yīng)的value?
可能是當(dāng)時(shí)對弱引用的理解不夠熟,所以產(chǎn)生了這個(gè)問題,如下面的代碼。
public class TestDemo { static ThreadLocal threadLocal = new ThreadLocal(); public static void main(String[] args) { threadLocal.set("demo"); System.gc(); System.out.println(threadLocal.get()); // demo } }
為什么還獲取到值,不是說在發(fā)現(xiàn)一次GC,弱引用就會(huì)被清除掉嗎?
糊涂了。
弱引用只有在該對象沒有被其它地方強(qiáng)引用的時(shí)候,才會(huì)被GC。
上述的原因就是因?yàn)?,很明顯,ThreadLocal對象除了被key弱引用,還由一個(gè)Reference強(qiáng)引用指向它,所以肯定不會(huì)被GC。
如果是這樣,那下一次GC,這個(gè)對象就被干掉了。
舉一個(gè)簡單的例子,幫助理解:
WeakReference<User> userWeakReference = new WeakReference<User>(new User("jack")); System.out.println(userWeakReference.get() == null); // false System.gc(); System.out.println(userWeakReference.get() == null); // true
很明顯,GC的時(shí)候直接清除了這個(gè)弱引用對象。
userWeakReference.get(), 如果此方法為空, 那么說明weakReference指向的對象已經(jīng)被回收了。
WeakReference<User> userWeakReference = new WeakReference<User>(new User("jack")); User jack = userWeakReference.get(); System.out.println(userWeakReference.get() == null); // false System.gc(); System.out.println(userWeakReference.get() == null); // false
當(dāng)我們添加了一個(gè)強(qiáng)引用來指向它的時(shí)候,該對象并不會(huì)被gc清除(弱引用還在)。
到此這篇關(guān)于ThreadLocal的內(nèi)存泄露問題的文章就介紹到這了,更多相關(guān)ThreadLocal內(nèi)存泄露內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Elasticsearch?mapping?概念及自動(dòng)創(chuàng)建示例
這篇文章主要為大家介紹了Elasticsearch?mapping?概念及自動(dòng)創(chuàng)建示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Java實(shí)現(xiàn)統(tǒng)計(jì)文檔中關(guān)鍵字出現(xiàn)的次數(shù)
這篇文章主要為大家分享了利用Java語言實(shí)現(xiàn)統(tǒng)計(jì)關(guān)鍵字在文檔中出現(xiàn)的次數(shù)的方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-05-05Java線程的生命周期命名與獲取代碼實(shí)現(xiàn)
這篇文章主要介紹了Java線程的生命周期命名與獲取代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java實(shí)現(xiàn)獲取cpu、內(nèi)存、硬盤、網(wǎng)絡(luò)等信息的方法示例
這篇文章主要介紹了Java實(shí)現(xiàn)獲取cpu、內(nèi)存、硬盤、網(wǎng)絡(luò)等信息的方法,涉及java使用第三方j(luò)ar包針對本機(jī)硬件的cpu、內(nèi)存、硬盤、網(wǎng)絡(luò)信息等的讀取相關(guān)操作技巧,需要的朋友可以參考下2018-06-06springboot 緩存@EnableCaching實(shí)例
這篇文章主要介紹了springboot 緩存@EnableCaching實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Idea工具中創(chuàng)建 SpringBoot工程及入門詳解
這篇文章主要介紹了Idea工具中創(chuàng)建 SpringBoot工程及入門分析詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02Java程序初始化啟動(dòng)自動(dòng)執(zhí)行的三種方式
這篇文章主要介紹了Java程序初始化啟動(dòng)自動(dòng)執(zhí)行的三種方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01