ThreadLocal 你怎么動(dòng)不動(dòng)就內(nèi)存泄漏?
本文轉(zhuǎn)載自微信公眾號(hào)「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java極客技術(shù)公眾號(hào)。
如果說 ThreadLocal 的話,那肯定就會(huì)涉及到內(nèi)存泄漏,為啥嘞
因?yàn)?吧啦吧啦 ~
ThreadLocal 解決了什么問題呢?
它是為了解決對(duì)象不能被多線程共享訪問的問題,通過 threadLocal.set() 方法將對(duì)象實(shí)例保存在每個(gè)線程自己所擁有的 threadLocalMap 中,這樣的話每個(gè)線程都使用自己的對(duì)象實(shí)例,彼此不會(huì)影響從而達(dá)到了隔離的作用,這樣就解決了對(duì)象在被共享訪問時(shí)帶來的線程安全問題。
啥意思呢?打個(gè)比方,現(xiàn)在公司所有人都要填寫一個(gè)表格,但是只有一支筆,這個(gè)時(shí)候就只能上個(gè)人用完了之后,下個(gè)人才可以使用,為了保證"筆"這個(gè)資源的可用性,只需要保證在接下來每個(gè)人的獲取順序就可以了,這就是 lock 的作用,當(dāng)這支筆被別人用的時(shí)候,我就加 lock ,你來了那就進(jìn)入隊(duì)列排隊(duì)等待獲取資源(非公平方式那就另外說了),這支筆用完之后就釋放 lock ,然后按照順序給下個(gè)人使用。
但是完全可以一個(gè)人一支筆對(duì)不對(duì),這樣的話,你填寫你的表格,我填寫我的表格,咱倆誰(shuí)都不耽擱誰(shuí)。這就是 ThreadLocal 在做的事情,因?yàn)槊總€(gè) Thread 都有一個(gè)副本,就不存在資源競(jìng)爭(zhēng),所以也就不需要加鎖,這不就是拿空間去換了時(shí)間嘛!
在開始之前,咱們先把 Thread, ThreadLocal, ThreadLocalMap 的關(guān)系捋一捋:
可以看到,在 Thread 中持有一個(gè) ThreadLocalMap , ThreadLocalMap 又是由 Entry 來組成的,在 Entry 里面有 ThreadLocal 和 value
ThreadLocal 為啥動(dòng)不動(dòng)就內(nèi)存泄漏呢?
在這里先給個(gè)解釋,后面咱們?cè)僭敿?xì)分析:
首先是因?yàn)?ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的,其中 ThreadLocalMap 的 Entry 繼承了 WeakReference ,而 Entry 對(duì)象中的 key 使用了 WeakReference 封裝,也就是說, Entry 中的 key 是一個(gè)弱引用類型,對(duì)于弱引用來說,它只能存活到下次 GC 之前
如果此時(shí)一個(gè)線程調(diào)用了 ThreadLocalMap 的 set 設(shè)置變量,當(dāng)前的 ThreadLocalMap 就會(huì)新增一條記錄,但由于發(fā)生了一次垃圾回收,這樣就會(huì)造成一個(gè)結(jié)果: key 值被回收掉了,但是 value 值還在內(nèi)存中,而且如果線程一直存在的話,那么它的 value 值就會(huì)一直存在
這樣被垃圾回收掉的 key 就會(huì)一直存在一條引用鏈: Thread -> ThreadLocalMap -> Entry -> Value :
就是因?yàn)檫@條引用鏈的存在,就會(huì)導(dǎo)致如果 Thread 還在運(yùn)行,那么 Entry 不會(huì)被回收,進(jìn)而 value 也不會(huì)被回收掉,但是 Entry 里面的 key 值已經(jīng)被回收掉了
這只是一個(gè)線程,如果再來一個(gè)線程,又來一個(gè)線程…多了之后就會(huì)造成內(nèi)存泄漏
知道是怎么造成內(nèi)存泄漏之后,接下來要做的事情就好說了,不是因?yàn)?value 值沒有被回收掉所以才會(huì)導(dǎo)致內(nèi)存泄露的嘛
那使用完 key 值之后,將 value 值通過 remove 方法 remove 掉,這樣的話內(nèi)存中就不會(huì)有 value 值了,也就防止了內(nèi)存泄漏嘛
ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的?
OK ,上面的內(nèi)容講完了,接下來一一來看
首先,你怎么知道 ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的呢?
從源碼知道的~
在源碼中能夠看到下面這幾行代碼:
- public class ThreadLocal<T> {
- static class ThreadLocalMap {
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- }
- }
代碼中說的很清楚了,在 ThreadLocal 內(nèi)部維護(hù)著 ThreadLocalMap ,而它的 Entry 則繼承自 WeakReference 的 ThreadLocal ,其中 Entry 的 k 為 ThreadLocal , v 為 Object ,在調(diào)用 super(k) 時(shí)就將 ThreadLocal 實(shí)例包裝成了一個(gè) WeakReference
強(qiáng)弱引用這塊內(nèi)容阿粉就直接放一個(gè)表格吧:
引用類型 | 功能特點(diǎn) |
---|---|
強(qiáng)引用 ( Strong Reference ) | 被強(qiáng)引用關(guān)聯(lián)的對(duì)象永遠(yuǎn)不會(huì)被垃圾回收器回收掉 |
軟引用( Soft Reference ) | 軟引用關(guān)聯(lián)的對(duì)象,只有當(dāng)系統(tǒng)將要發(fā)生內(nèi)存溢出時(shí),才會(huì)去回收軟引用引用的對(duì)象 |
弱引用 ( Weak Reference ) | 只被弱引用關(guān)聯(lián)的對(duì)象,只要發(fā)生垃圾收集事件,就會(huì)被回收 |
虛引用 ( Phantom Reference ) | 被虛引用關(guān)聯(lián)的對(duì)象的唯一作用是能在這個(gè)對(duì)象被回收器回收時(shí)收到一個(gè)系統(tǒng)通知 |
從表格中應(yīng)該能夠看出來,弱引用的對(duì)象只要發(fā)生垃圾收集事件,就會(huì)被回收
所以弱引用的存活時(shí)間也就是下次 GC 之前了
在這里阿粉就有個(gè)問題想問問了:為什么 ThreadLocal 采用弱引用,而不是強(qiáng)引用嘞?
在 ThreadLocalMap 上面有些注釋,我在這里摘錄一部分,或許可以從中窺探一二:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys
翻譯一下就是:(雖然我英語(yǔ)不是很好
為了解決非常大且長(zhǎng)期使用的問題,哈希表使用了弱引用的 key
假設(shè),假設(shè), ThreadLocal 使用的是強(qiáng)引用,會(huì)怎樣呢?
如果是強(qiáng)引用的話,在表格中也能夠看出來,被強(qiáng)引用關(guān)聯(lián)的對(duì)象,永遠(yuǎn)都不會(huì)被垃圾回收器回收掉
如果引用的 ThreadLocal 對(duì)象被回收了,但是 ThreadLocalMap 還持有對(duì) ThreadLocal 的強(qiáng)引用,如果沒有 remove 的話, 在 GC 時(shí)進(jìn)行可達(dá)性分析, ThreadLocal 依然可達(dá),這樣就不會(huì)對(duì) ThreadLocal 進(jìn)行回收,但是我們期望的是引用的 ThreadLocal 對(duì)象被回收,這樣不就達(dá)不到目的了嘛
使用弱引用的話,雖然會(huì)出現(xiàn)內(nèi)存泄漏的問題,但是在 ThreadLocal 生命周期里面,都有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作
所以,使用弱引用的話,可以在 ThreadLocal 生命周期中盡可能保證不出現(xiàn)內(nèi)存泄漏的問題
啥?在 ThreadLcoal 生命周期里面,都有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作?有證據(jù)么?