ThreadLocal的內(nèi)存泄露問題
ThreadLocal的內(nèi)部實現(xiàn)
在每一個線程Thread對象中,都維護了一個ThreadLocalMap對象。

ThreadLocalMap中又維護了一個k v 形式的Entry對象,key指向了當前ThreadLocal對象,value就是我們實際在ThreadLocal中存儲的值。

注意,這里的Entry中的key存放是ThreadLocal的弱引用。

實現(xiàn)指的是強引用,虛線指的是弱引用。
其實際上,ThreaLocal本身是不存儲值的,我們在使用其對應的set、get方法時,都是操作的其對應的ThreadLocalMap對象。
為什么會出現(xiàn)內(nèi)存泄露?
從上述可以看到,在Entry中的key存儲的ThreadLocal的弱引用。
弱引用在發(fā)生GC時,就會被垃圾回收掉,具體可以參考JVM相關的知識。
所以,在當前線程正在運行的時候,發(fā)生GC時,在ThreadLocal對象沒有被其它地方強引用時,key指向ThreadLocal的虛引用就會立即斷開(被垃圾回收掉),這時,就會出現(xiàn)ThreadLocalMap中存在key為null的Entry,只要當前線程不結(jié)束,該ThreadLocalMap對象就會一直存在,永遠無法回收,因為此時還存在一條強引用的鏈路,從圖中也可以發(fā)現(xiàn):
Current Thread Reference --> Current Thread --> ThreadLocalMap --> EntryValue --> Object
所以這個時候就造成了內(nèi)存泄露。
Entry對象的key為什么要使用弱引用,有什么好處?
在上述所說的問題中,即使ThreadLocalMap中存在key為null的Entry,但是該Entry的value值并不會因為GC而被回收(value存本身就存著一個強引用的對象),所以就導致了該對象不會被回收掉而出現(xiàn)了內(nèi)存泄露。
其實,ThreadLocalMap在設計時就考慮到了這個方面,它也采取了一些措施來避免這種key為null,而value不為null的對象占用內(nèi)存,在我們調(diào)用ThreadLocal的set、get、remove方法時,都會將這些key為null的對象清空掉,避免因為這種情況而導致內(nèi)存泄露。
這也就是為什么key要存儲弱引用的原因。
假設如果存儲的強引用,我們斷開ThreadLocal Reference —> ThreadLocal的引用,會發(fā)現(xiàn)key強引用了ThreadLocal,導致該對象永遠無法被GC。

但是,即使上述提供了避免內(nèi)存泄露的措施,但是不能完全避免,比如以下的情況:
- 分配了ThreadLocal對象,但是并沒有執(zhí)行其get、set、remove方法,導致不能有效的清除null對象;
- 使用線程池的情況下,使用完ThreadLocal一定要使用remove方法即時清理,因為ThreadLocal是屬于某個線程的,而在使用線程池的情況下,這些線程都是可重復利用、存活時間長的線程,如果在使用過程中不僅從即使的remove,那么不僅會造成內(nèi)存泄露的問題,還會引發(fā)一些功能邏輯問題,比如,B請求可能和A請求分配到了線程池中的同一個線程,那么它們拿到的ThreadLocal就是一樣的。
set:

cleanSomeSlots:

get:

關于弱引用的一些知識補充
學習的過程中想到了一個問題,弱引用會不會導致運行過程中GC清除key,導致找不到對應的value?
可能是當時對弱引用的理解不夠熟,所以產(chǎn)生了這個問題,如下面的代碼。
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,弱引用就會被清除掉嗎?
糊涂了。
弱引用只有在該對象沒有被其它地方強引用的時候,才會被GC。
上述的原因就是因為,很明顯,ThreadLocal對象除了被key弱引用,還由一個Reference強引用指向它,所以肯定不會被GC。

如果是這樣,那下一次GC,這個對象就被干掉了。

舉一個簡單的例子,幫助理解:
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的時候直接清除了這個弱引用對象。
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
當我們添加了一個強引用來指向它的時候,該對象并不會被gc清除(弱引用還在)。
到此這篇關于ThreadLocal的內(nèi)存泄露問題的文章就介紹到這了,更多相關ThreadLocal內(nèi)存泄露內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java數(shù)據(jù)庫連接池連接Oracle過程詳解
這篇文章主要介紹了Java數(shù)據(jù)庫連接池連接Oracle過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-09-09
Javaweb-HttpServletResponse的sendRedirectch重定向方式
這篇文章主要介紹了Javaweb-HttpServletResponse的sendRedirectch重定向方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09
Springboot項目中運用vue+ElementUI+echarts前后端交互實現(xiàn)動態(tài)圓環(huán)圖(推薦)
今天給大家?guī)硪黄坛剃P于Springboot項目中運用vue+ElementUI+echarts前后端交互實現(xiàn)動態(tài)圓環(huán)圖的技能,包括環(huán)境配置及圓環(huán)圖前端后端實現(xiàn)代碼,感興趣的朋友一起看看吧2021-06-06
關于Socket的解析以及雙方即時通訊的java實現(xiàn)方法
本篇文章主要介紹了關于Socket的解析以及雙方通訊的java實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03

