關(guān)于ThreadLocal內(nèi)存泄露的備忘

還記得第一次接觸到ThreadLocal可能導(dǎo)致內(nèi)存泄露的問(wèn)題是有一次面試的時(shí)候被問(wèn)到了ThreadLocal的缺陷是什么。當(dāng)然由于后來(lái)沒(méi)有面試官的聯(lián)系方式很遺憾也一直沒(méi)能確認(rèn)所謂的缺陷是不是就是可能導(dǎo)致內(nèi)存泄漏蛤售,不過(guò)后來(lái)發(fā)現(xiàn)雖然當(dāng)時(shí)弄明白了可是過(guò)段時(shí)間又搞忘記了這個(gè)問(wèn)題税朴,所以特別記錄下來(lái)做個(gè)備忘吧宛渐。

ThreadLocal從名字上來(lái)說(shuō)就很好理解,就是用于線(xiàn)程(Thread)私有(Local)的存儲(chǔ)結(jié)構(gòu),這種結(jié)構(gòu)能夠使得線(xiàn)程能夠使用只有自己能夠訪(fǎng)問(wèn)和修改的變量饿凛,從而實(shí)現(xiàn)多個(gè)線(xiàn)程之間的資源互相隔離只泼,達(dá)到安全并發(fā)的目的剖笙。

也因此,ThreadLocal作為線(xiàn)程并發(fā)中的一種資源使用方式请唱,得到了很廣泛的應(yīng)用弥咪,比如Spring MVC、Hibernate等十绑。
不過(guò)值得一提的是聚至,通常有人會(huì)講ThreadLocal和synchronised等放在一起,作為形成安全并發(fā)的手段之一本橙。其實(shí)我覺(jué)得這是比較容易使人誤導(dǎo)的扳躬,因?yàn)閮烧叩哪康男酝耆灰粯印?br> ThreadLocal主要的是用于獨(dú)享自己的變量,避免一些資源的爭(zhēng)奪甚亭,從而實(shí)現(xiàn)了空間換時(shí)間的思想贷币。
而synchronised則主要用于臨界(沖突)資源的分配,從而能夠?qū)崿F(xiàn)線(xiàn)程間信息同步亏狰,公共資源共享等役纹,所以嚴(yán)格來(lái)說(shuō)synchronised其實(shí)是能夠?qū)崿F(xiàn)ThreadLocal所需要的達(dá)到的效果的,只不過(guò)這樣會(huì)帶來(lái)資源爭(zhēng)奪導(dǎo)致并發(fā)性能下降暇唾,而且還有synchronised促脉、線(xiàn)程切換等一些可能不必要的開(kāi)銷(xiāo)。


對(duì)于ThreadLocal而言策州,其實(shí)使用起來(lái)有點(diǎn)像基礎(chǔ)類(lèi)型的裝箱類(lèi)型的感覺(jué)(個(gè)人覺(jué)得其實(shí)也可以算是一種裝飾器模式的使用嘲叔?),具體的使用就不在啰嗦了抽活。下面就看看這次備忘的重點(diǎn)硫戈,如何導(dǎo)致內(nèi)存泄漏的。

其實(shí)網(wǎng)上有的文章已經(jīng)講的聽(tīng)清楚的下硕,覺(jué)得有張圖特別好先引用到這里丁逝,來(lái)源于ThreadLocal可能引起的內(nèi)存泄露

[ThreadLocal可能引起的內(nèi)存泄露](http://www.cnblogs.com/onlywujun/p/3524675.html)

所以簡(jiǎn)單的說(shuō),主要原因就是在于TreadLocal中用到的自己定義的Map(和常用的Map接口不同)中梭姓,使用的Key值是一個(gè)WeakReference類(lèi)型的值(弱引用會(huì)在下一次GC時(shí)馬上釋放而不管是否被引用)霜幼。那么如果這個(gè)Key在GC時(shí)被釋放了,就會(huì)導(dǎo)致Value永遠(yuǎn)都不會(huì)被調(diào)用到誉尖,但是如果線(xiàn)程不結(jié)束罪既,又一直存在。

因?yàn)榭赡懿皇煜み@部分內(nèi)容的同學(xué)(例如幾周以后的我)會(huì)感覺(jué)有點(diǎn)迷糊為什么這個(gè)圖是這樣的,就具體再解釋一下細(xì)節(jié)點(diǎn):

  • 首先當(dāng)然是看一下我們的主角ThreadLocal類(lèi)琢感,只保留了幾個(gè)重點(diǎn)的地方丢间,特別的是內(nèi)部靜態(tài)類(lèi)的ThreadLocalMap是ThreadLocal自己實(shí)現(xiàn)的一個(gè)Map,而這個(gè)Map用使用了ThreadLocal作為了一個(gè)弱引用的Key(也就是主要問(wèn)題點(diǎn))驹针。
    p.s.不知道各位第一次看的時(shí)候會(huì)不會(huì)跟我一樣有種我是老子的兒子的同時(shí)又是老子的老子的感覺(jué)烘挫,哈哈哈
public class ThreadLocal<T> {
    
    //  獲取Thread里面的Map
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    // (敲黑板)
    // 這里是重點(diǎn)!<砩饮六!
    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;
            }
        }
        ... 
    }
    ... 
 }
  • 接著不得不說(shuō)的就是我們的大佬Thread類(lèi),里面關(guān)于ThreadLocal部分的內(nèi)容主要是這樣滴臂外。我們可以看到這里主要是聲明了ThreadLocal里面的Map作為類(lèi)變量來(lái)提供給線(xiàn)程使用的虽风。也正式因?yàn)槿绱耍艜?huì)在ThreadLocal里面的getMap方法是拉取的Thread里面的Map寄月。
    p.s. 感覺(jué)確實(shí)有點(diǎn)繞
public class Thread implements Runnable {

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • 于是到這里我們就明白了,其實(shí)每個(gè)Thread里面都有一個(gè)Map无牵,Map里面的Key是ThreadLocal類(lèi)的一個(gè)實(shí)例漾肮,之所以會(huì)比較混淆主要還是因?yàn)檫@里的Map又是ThreadLocal里面的一個(gè)內(nèi)部靜態(tài)類(lèi)。

所以到這里其實(shí)有兩個(gè)問(wèn)題是暫時(shí)還沒(méi)想通的茎毁,也希望有各位大佬指點(diǎn)一二:

  1. TreadLocalMap 其實(shí)是可以抽取成單獨(dú)的類(lèi)的克懊?這樣就使得邏輯和嵌套關(guān)系沒(méi)有這么繞的感覺(jué)。
  2. 為什么只有Key要設(shè)計(jì)成WeakReference而不是Key和Value都是七蜘,或者這里為什么要設(shè)置弱引用谭溉?如果為了保護(hù)內(nèi)存空間其實(shí)兩者都是弱引用更好吧,是不是有什么其它考慮橡卤?

回歸到內(nèi)存泄露是因?yàn)閃eakReference Key的問(wèn)題扮念,當(dāng)然,Java的各位大佬肯定早就想到這個(gè)問(wèn)題了碧库,可以看到人家注釋里面是這么說(shuō)的柜与,大意就是如果key==null的時(shí)候,就可以認(rèn)為這個(gè)值無(wú)效了嵌灰,可以調(diào)用expunged進(jìn)行清理:

/**
  * The entries in this hash map extend WeakReference, using
  * its main ref field as the key (which is always a
  * ThreadLocal object).  Note that null keys (i.e. entry.get()
  * == null) mean that the key is no longer referenced, so the
  * entry can be expunged from table.  Such entries are referred to
  * as "stale entries" in the code that follows.
  */

而這個(gè)expungeStaleEntry方法在get弄匕、set時(shí)都會(huì)有間接的調(diào)用,而且remove方法中也會(huì)顯示的調(diào)用沽瞭,這也就是為什么有的文章中說(shuō)通過(guò)在線(xiàn)程調(diào)用完成之后迁匠,通過(guò)調(diào)用remove方法能有效的杜絕該泄露問(wèn)題的原因。

當(dāng)然簡(jiǎn)單來(lái)說(shuō)理解到這里就基本明了內(nèi)存泄露的原因,但是其實(shí)再深入一點(diǎn)來(lái)說(shuō)城丧,如果泄露的原因是Key被釋放延曙,而Value沒(méi)有釋放,那么是否一定會(huì)有泄露呢芙贫?
答案當(dāng)然是否定的搂鲫,因?yàn)槿绻且话愕木€(xiàn)程場(chǎng)景中,除了會(huì)調(diào)用expungeStaleEntry來(lái)進(jìn)行清理磺平,最差魂仍,在線(xiàn)程結(jié)束之時(shí),自然也就消除了引用從而使得Value得以GC回收拣挪。

所以擦酌,會(huì)不會(huì)有線(xiàn)程一直不結(jié)束的場(chǎng)景呢?
當(dāng)然答案是肯定的菠劝,最簡(jiǎn)單來(lái)說(shuō)線(xiàn)程只要一直在wait就不會(huì)結(jié)束了赊舶,不過(guò)這種場(chǎng)景下其實(shí)和泄露也沒(méi)啥關(guān)系的感覺(jué)。
其實(shí)最常用的線(xiàn)程一直不結(jié)束的場(chǎng)景赶诊,自然就是線(xiàn)程池了笼平。因?yàn)檫@種情況下,線(xiàn)程是一直在不斷的重復(fù)運(yùn)行的舔痪,從而也就造成了value可能造成累積的情況寓调。具體的模擬可以參考: 深入理解ThreadLocal的"內(nèi)存溢出"


最后來(lái)做個(gè)總結(jié)吧,可能泄露的場(chǎng)景僅且僅在:

  1. 線(xiàn)程run方法結(jié)束后沒(méi)有顯示的調(diào)用remove進(jìn)行清理
  2. 線(xiàn)程在線(xiàn)程池的模式下锄码,一直重復(fù)運(yùn)行
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夺英,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滋捶,更是在濱河造成了極大的恐慌痛悯,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件重窟,死亡現(xiàn)場(chǎng)離奇詭異载萌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)巡扇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)炒考,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人霎迫,你說(shuō)我怎么就攤上這事斋枢。” “怎么了知给?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵瓤帚,是天一觀(guān)的道長(zhǎng)描姚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)戈次,這世上最難降的妖魔是什么轩勘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮怯邪,結(jié)果婚禮上绊寻,老公的妹妹穿的比我還像新娘。我一直安慰自己悬秉,他們只是感情好澄步,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著和泌,像睡著了一般村缸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上武氓,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天梯皿,我揣著相機(jī)與錄音,去河邊找鬼县恕。 笑死东羹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忠烛。 我是一名探鬼主播属提,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼况木!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旬迹,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤火惊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后奔垦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體屹耐,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年椿猎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惶岭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犯眠,死狀恐怖按灶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情筐咧,我是刑警寧澤鸯旁,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布噪矛,位于F島的核電站,受9級(jí)特大地震影響铺罢,放射性物質(zhì)發(fā)生泄漏艇挨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一韭赘、第九天 我趴在偏房一處隱蔽的房頂上張望缩滨。 院中可真熱鬧,春花似錦泉瞻、人聲如沸脉漏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸠删。三九已至,卻和暖如春贼陶,著一層夾襖步出監(jiān)牢的瞬間刃泡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工碉怔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留烘贴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓撮胧,卻偏偏與公主長(zhǎng)得像桨踪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芹啥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容