收不回来的value
ThreadLocal注意點
內存泄漏問題
內存泄漏:某個對象不再有用,但是占用的內存確不能被回收。
Key的泄漏
ThreadLocalMap中的Entry繼承自WeakReference,是弱引用,弱引用的特點是,如果這個對象只被弱引用關聯(沒有任何強引用關聯),那么在下一次垃圾回收的時候必然會被清理掉。?
Value的泄漏
- ThreadLocalMap的每個Entry都是一個對Key的弱引用,同時每個Entry都包含了一個對Value的強引用
- 正常情況下,當線程終止,保存在ThreadLocal里的Value會被垃圾回收,因為沒有任何強引用關聯
-
但是,如果線程始終不終止(比如線程需要保持很久或者使用線程池的時候),那么Key對應的Value就不能被回收,因為有以下的調用鏈:
- 因為Value和Thread之間還存在這個強引用調用鏈,所以導致value無法被回收,就可會出現OOM
- JDK已經考慮到了這個問題,所以在set()、remove()、rehash()方法中會掃描key為null的Entry,并把對應的value設置為null,這樣value對象就可以被回收
- 但是如果一個ThreadLocal不被使用,那么實際上set()、remove()、rehash()等方法也不會被調用,并且線程又不停止,那么調用鏈就一直存在,那么也就導致了value的內存泄漏
如何避免內存泄漏(阿里代碼規約)
- 調用remove()方法,就會刪除對應的Entry對象,可以避免內存泄漏,所以使用完ThreadLocal之后,應該調用remove()方法
- 如果使用攔截器獲取用戶信息,那么同樣應該使用攔截器在線程請求退出之前將之前保存過得信息清除掉
?
ThreadLocal引用對象存在兩處:ThreadLocal tl = new ThreadLocal;? ?ThreadMap
ThreadMap跟著Thread,Thread如果在線程池中,則不會銷毀棧中的引用對象;
所以ThreadLocal對象存在內存泄漏的情況,Map中設計成弱引用則可避免,但未解決value的內存泄漏(1)
jdk在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。(注意,是使之失效,下次gc時回收)但仍存在階段的泄漏風險(2)
故最佳實踐:每次使用完ThreadLocal,都調用它的remove()方法,
(1)避免key的泄漏
(2)避免value的泄漏
可以看到jdk設計是比較嚴謹安全的,假定程序員set完了不會remove(如果ThreadLocal ref修飾為static,則必須remove?ThreadLocal為什么要設計成private static?)
前言
ThreadLocal?的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。但是如果濫用?ThreadLocal,就可能會導致內存泄漏。下面,我們將圍繞三個方面來分析?ThreadLocal?內存泄漏的問題
- ThreadLocal?實現原理
- ThreadLocal為什么會內存泄漏
- ThreadLocal?最佳實踐
ThreadLocal 實現原理
?
ThreadLocal的實現是這樣的:每個Thread?維護一個?ThreadLocalMap?映射表,這個映射表的?key?是?ThreadLocal?實例本身,value?是真正需要存儲的Object。
也就是說?ThreadLocal?本身并不存儲值,它只是作為一個?key?來讓線程從?ThreadLocalMap?獲取?value。值得注意的是圖中的虛線,表示ThreadLocalMap?是使用?ThreadLocal?的弱引用作為?Key?的,弱引用的對象在 GC 時會被回收。
ThreadLocal為什么會內存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用來引用它,那么系統 GC 的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內存泄漏。
其實,ThreadLocalMap的設計中已經考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value。
但是這些被動的預防措施并不能保證不會內存泄漏:
- 使用線程池的時候,這個線程執行任務結束,ThreadLocal對象被回收了,線程放回線程池中不銷毀,1?這個線程一直不被使用,導致內存泄漏。
- 2 分配使用了ThreadLocal又不再調用get(),set(),remove()方法,那么這個期間就會發生內存泄漏。
為什么使用弱引用
從表面上看內存泄漏的根源在于使用了弱引用。網上的文章大多著重分析為什么會內存泄漏,但是另一個問題也同樣值得思考:為什么使用弱引用?為什么不用強引用?
static?class?Entry?extends?WeakReference<ThreadLocal>
我們先來看看官方文檔的說法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了應對非常大和長時間的用途,哈希表使用弱引用的 key。
下面我們分兩種情況討論:
- key 使用強引用:引用的ThreadLocal的對象被回收了,但是ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏。
- key 使用弱引用:引用的ThreadLocal的對象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收。value在下一次ThreadLocalMap調用set,get的時候會被清除。
比較兩種情況,我們可以發現:由于ThreadLocalMap的生命周期跟Thread一樣長,如果都沒有手動刪除對應key,都會導致內存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove的時候會被清除。
因此,ThreadLocal內存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用。
ThreadLocal 最佳實踐
綜合上面的分析,我們可以理解ThreadLocal內存泄漏的前因后果,那么怎么避免內存泄漏呢?
- 每次使用完ThreadLocal,都調用它的remove()方法,清除數據。
在使用線程池的情況下,沒有及時清理ThreadLocal,不僅是內存泄漏的問題,更嚴重的是可能導致業務邏輯出現問題。所以,使用ThreadLocal就跟加鎖完要解鎖一樣,用完就清理。
?
總結
以上是生活随笔為你收集整理的收不回来的value的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两种场景,殊途同归
- 下一篇: ThreadLocal的空指针异常问题