「漲薪秘訣」ThreadLocal的內(nèi)存泄露的原因分析+避免方法

前言

在分析ThreadLocal導(dǎo)致的內(nèi)存泄露前,需要普及了解一下內(nèi)存泄露、強(qiáng)引用與弱引用以及GC回收機(jī)制,這樣才能更好的分析為什么ThreadLocal會(huì)導(dǎo)致內(nèi)存泄露呢?更重要的是知道該如何避免這樣情況發(fā)生七问,增強(qiáng)系統(tǒng)的健壯性。

內(nèi)存泄露

內(nèi)存泄露為程序在申請內(nèi)存后茫舶,無法釋放已申請的內(nèi)存空間械巡,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重饶氏,無論多少內(nèi)存,遲早會(huì)被占光讥耗,

廣義并通俗的說,就是:不再會(huì)被使用的對象或者變量占用的內(nèi)存不能被回收嚷往,就是內(nèi)存泄露葛账。

強(qiáng)引用與弱引用

強(qiáng)引用,使用最普遍的引用皮仁,一個(gè)對象具有強(qiáng)引用籍琳,不會(huì)被垃圾回收器回收。當(dāng)內(nèi)存空間不足贷祈,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤趋急,使程序異常終止,也不回收這種對象势誊。

如果想取消強(qiáng)引用和某個(gè)對象之間的關(guān)聯(lián)呜达,可以顯式地將引用賦值為null,這樣可以使JVM在合適的時(shí)間就會(huì)回收該對象粟耻。

弱引用查近,JVM進(jìn)行垃圾回收時(shí)眉踱,無論內(nèi)存是否充足,都會(huì)回收被弱引用關(guān)聯(lián)的對象霜威。在java中谈喳,用java.lang.ref.WeakReference類來表示。可以在緩存中使用弱引用。

GC回收機(jī)制-如何找到需要回收的對象

JVM如何找到需要回收的對象寺渗,方式有兩種:

引用計(jì)數(shù)法:每個(gè)對象有一個(gè)引用計(jì)數(shù)屬性,新增一個(gè)引用時(shí)計(jì)數(shù)加1扭倾,引用釋放時(shí)計(jì)數(shù)減1,計(jì)數(shù)為0時(shí)可以回收挽绩,

可達(dá)性分析法:從 GC Roots 開始向下搜索膛壹,搜索所走過的路徑稱為引用鏈。當(dāng)一個(gè)對象到 GC Roots 沒有任何引用鏈相連時(shí)琼牧,則證明此對象是不可用的恢筝,那么虛擬機(jī)就判斷是可回收對象哀卫。

引用計(jì)數(shù)法巨坊,可能會(huì)出現(xiàn)A 引用了 B,B 又引用了 A此改,這時(shí)候就算他們都不再使用了趾撵,但因?yàn)橄嗷ヒ?計(jì)數(shù)器=1 永遠(yuǎn)無法被回收。

ThreadLocal的內(nèi)存泄露分析

先從前言的了解了一些概念(已懂忽略)共啃,接下來我們開始正式的來理解ThreadLocal導(dǎo)致的內(nèi)存泄露的解析占调。

實(shí)現(xià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;? ? ? ? }? ? }? ? ...? }

ThreadLocal的實(shí)現(xiàn)原理,每一個(gè)Thread維護(hù)一個(gè)ThreadLocalMap移剪,key為使用?弱引用?的ThreadLocal實(shí)例究珊,value為線程變量的副本。這些對象之間的引用關(guān)系如下,

實(shí)心箭頭表示強(qiáng)引用纵苛,空心箭頭表示弱引用

ThreadLocal 內(nèi)存泄漏的原因

從上圖中可以看出剿涮,hreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal不存在外部?強(qiáng)引用?時(shí)攻人,Key(ThreadLocal)勢必會(huì)被GC回收取试,這樣就會(huì)導(dǎo)致ThreadLocalMap中key為null, 而value還存在著強(qiáng)引用怀吻,只有thead線程退出以后,value的強(qiáng)引用鏈條才會(huì)斷掉瞬浓。

但如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永遠(yuǎn)無法回收蓬坡,造成內(nèi)存泄漏猿棉。

那為什么使用弱引用而不是強(qiáng)引用磅叛??

我們看看Key使用的

key 使用強(qiáng)引用

當(dāng)hreadLocalMap的key為強(qiáng)引用回收ThreadLocal時(shí)萨赁,因?yàn)門hreadLocalMap還持有ThreadLocal的強(qiáng)引用宪躯,如果沒有手動(dòng)刪除,ThreadLocal不會(huì)被回收位迂,導(dǎo)致Entry內(nèi)存泄漏访雪。

key 使用弱引用

當(dāng)ThreadLocalMap的key為弱引用回收ThreadLocal時(shí),由于ThreadLocalMap持有ThreadLocal的弱引用掂林,即使沒有手動(dòng)刪除臣缀,ThreadLocal也會(huì)被回收。當(dāng)key為null泻帮,在下一次ThreadLocalMap調(diào)用set(),get()精置,remove()方法的時(shí)候會(huì)被清除value值。

ThreadLocalMap的remove()分析

在這里只分析remove()方式锣杂,其他的方法可以查看源碼進(jìn)行分析:

private void remove(ThreadLocal<?> key) {? ? //使用hash方式脂倦,計(jì)算當(dāng)前ThreadLocal變量所在table數(shù)組位置? ? Entry[] tab = table;? ? int len = tab.length;? ? int i = key.threadLocalHashCode & (len-1);? ? //再次循環(huán)判斷是否在為ThreadLocal變量所在table數(shù)組位置? ? for (Entry e = tab[i];? ? ? ? e != null;? ? ? ? e = tab[i = nextIndex(i, len)]) {? ? ? ? if (e.get() == key) {? ? ? ? ? ? //調(diào)用WeakReference的clear方法清除對ThreadLocal的弱引用? ? ? ? ? ? e.clear();? ? ? ? ? ? //清理key為null的元素? ? ? ? ? ? expungeStaleEntry(i);? ? ? ? ? ? return;? ? ? ? }? ? }}

再看看清理key為null的元素expungeStaleEntry(i):

private int expungeStaleEntry(int staleSlot) {? ? Entry[] tab = table;? ? int len = tab.length;? ? // 根據(jù)強(qiáng)引用的取消強(qiáng)引用關(guān)聯(lián)規(guī)則,將value顯式地設(shè)置成null元莫,去除引用? ? tab[staleSlot].value = null;? ? tab[staleSlot] = null;? ? size--;? ? // 重新hash赖阻,并對table中key為null進(jìn)行處理? ? Entry e;? ? int i;? ? for (i = nextIndex(staleSlot, len);? ? ? ? (e = tab[i]) != null;? ? ? ? i = nextIndex(i, len)) {? ? ? ? ThreadLocal<?> k = e.get();? ? ? ? //對table中key為null進(jìn)行處理,將value設(shè)置為null,清除value的引用? ? ? ? if (k == null) {? ? ? ? ? ? e.value = null;? ? ? ? ? ? tab[i] = null;? ? ? ? ? ? size--;? ? ? ? } else {? ? ? ? ? ? int h = k.threadLocalHashCode & (len - 1);? ? ? ? ? ? if (h != i) {? ? ? ? ? ? ? ? tab[i] = null;? ? ? ? ? ? ? ? while (tab[h] != null)? ? ? ? ? ? ? ? ? ? h = nextIndex(h, len);? ? ? ? ? ? ? ? tab[h] = e;? ? ? ? ? ? }? ? ? ? }? ? }? ? return i;}

總結(jié)

由于Thread中包含變量ThreadLocalMap踱蠢,因此ThreadLocalMap與Thread的生命周期是一樣長火欧,如果都沒有手動(dòng)刪除對應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏茎截。

但是使用 弱引用 可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏苇侵,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set(),get(),remove()的時(shí)候會(huì)被清除。

因此企锌,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長榆浓,如果沒有手動(dòng)刪除對應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

ThreadLocal正確的使用方法

每次使用完ThreadLocal都調(diào)用它的remove()方法清除數(shù)據(jù)

將ThreadLocal變量定義成private static撕攒,這樣就一直存在ThreadLocal的強(qiáng)引用陡鹃,也就能保證任何時(shí)候都能通過ThreadLocal的弱引用訪問到Entry的value值,進(jìn)而清除掉 打却。

最后

覺得此文不錯(cuò)的大佬們可以多多關(guān)注或者幫忙轉(zhuǎn)發(fā)分享一下哦杉适,感謝!A鳌T惩啤!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蹬叭,隨后出現(xiàn)的幾起案子藕咏,更是在濱河造成了極大的恐慌,老刑警劉巖秽五,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽查,死亡現(xiàn)場離奇詭異,居然都是意外死亡坦喘,警方通過查閱死者的電腦和手機(jī)盲再,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓣铣,“玉大人答朋,你說我怎么就攤上這事√男Γ” “怎么了梦碗?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蓖救。 經(jīng)常有香客問我洪规,道長,這世上最難降的妖魔是什么循捺? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任斩例,我火速辦了婚禮,結(jié)果婚禮上巨柒,老公的妹妹穿的比我還像新娘樱拴。我一直安慰自己,他們只是感情好洋满,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著珍坊,像睡著了一般牺勾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阵漏,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天驻民,我揣著相機(jī)與錄音,去河邊找鬼履怯。 笑死回还,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叹洲。 我是一名探鬼主播柠硕,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝗柔?” 一聲冷哼從身側(cè)響起闻葵,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎癣丧,沒想到半個(gè)月后槽畔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胁编,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年厢钧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嬉橙。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坏快,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憎夷,到底是詐尸還是另有隱情莽鸿,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布拾给,位于F島的核電站祥得,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蒋得。R本人自食惡果不足惜级及,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望额衙。 院中可真熱鬧饮焦,春花似錦、人聲如沸窍侧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伟件。三九已至硼啤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斧账,已是汗流浹背谴返。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咧织,地道東北人嗓袱。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像习绢,于是被迫代替她去往敵國和親渠抹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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