
閱讀本文前,需要先了解ThreadLocal的原理。
我們将從下面這些問題開始,尋找ThreadLocal中記憶體洩漏的罪魁禍首。
一、ThreadLocal,Thread,ThreadLocalMap,Entry<k,v>之間的關系?
先來看看Thread源碼
可以發現,每一個Thread中維護了一個ThreadLocalMap成員變量(也稱threadLocals)。
再來看看ThreadLocal中的源碼
不難看出ThreadLocalMap類是ThreadLocal類的靜态内部類,而Entry是ThreadLocalMap的靜态内部類,key是ThreadLocal(聲明為弱引用),value是Object,也就是我們要存的值。
二、ThreadLocal在預防記憶體洩漏方面,做了哪些努力?
Thread中維護了ThreadLocalMap,是以ThreadLocalMap的生命周期和Thread(目前線程)一樣長。使用不當就可能會導緻記憶體洩漏問題。但是,在ThreadLocal中,進行get,set操作的時候會清除Map裡所有key為null的value。
三、ThreadLocal的實作原理?
ThreadLocal自身并不儲存值,而是作為一個key來讓線程從ThreadLocal擷取value。Entry是中的key是弱引用,是以jvm在垃圾回收時如果外部沒有強引用來引用它,ThreadLocal必然會被回收。但是,作為ThreadLocalMap的key,ThreadLocal被回收後,ThreadLocalMap就會存在null,但value不為null的Entry。若目前線程一直不結束,可能是作為線程池中的一員,線程結束後不被銷毀,或者配置設定(目前線程又建立了ThreadLocal對象)使用了又不再調用get/set方法,就可能引發記憶體洩漏。其次,就算線程結束了,作業系統在回收線程或程序的時候不是一定殺死線程或程序的,在繁忙的時候,隻會清除線程或程序資料的操作,重複使用線程或程序(線程id可能不變導緻記憶體洩漏)。是以,
key弱引用并不是導緻記憶體洩漏的原因,而是因為ThreadLocalMap的生命周期與目前線程一樣長,并且沒有手動删除對應key。那麼,為什麼要将Entry中的key設為弱引用?相反,設定為弱引用的key能預防大多數記憶體洩漏的情況。如果key 使用強引用,引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動删除,ThreadLocal不會被回收,導緻Entry記憶體洩漏。如果key為弱引用,引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動删除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
四、如何避免上述弱引用引發的記憶體洩漏?
在使用完ThreadLocal時,及時調用它的的remove方法清除資料。
總而言之,如果開發者希望将類的某個靜态變量與線程狀态關聯,可以考慮使用ThreadLocal。ThreadLocal的設計本身就是為了能夠在目前線程中有屬于自己的變量,并不是為了解決并發或者共享變量的問題。
部落客常年線上,如有錯誤,歡迎評論指出。
如果喜歡我的文章歡迎關注我的專欄~