1. 程式人生 > >ThreadLocal的記憶體洩露

ThreadLocal的記憶體洩露

ThreadLocal的目的就是為每一個使用ThreadLocal的執行緒都提供一個值,讓該值和使用它的執行緒繫結,當然每一個執行緒都可以獨立地改變它繫結的值。如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程式. 關於的ThreadLocal更多內容,請參考《》。 在閱讀了ThreadLocal的原始碼後,我發現如果我們使用不恰當,可能造成記憶體洩露。經我測試,記憶體洩露的確存在。雖然該記憶體洩露,理論上上已經不算嚴重。 測試程式碼如下 ThreadLocalTest檔案 package com.teleca.robin; public class
ThreadLocalTest { publicThreadLocalTest() { } ThreadLocal<Content> tl=new ThreadLocal<Content> (); voidstart() { System.out.println("begin"); Content content=tl.get(); if(content==null) { content= new Content(); tl.set(content); } System.out.println("try to release content data"); //tl.set(null)
;//@1 //tl.remove();//@2 tl=null;//@3 content=null;//@4 System.out.println("request gc"); System.gc(); try { Thread.sleep(1000); catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); } } class Content { byte data[]=new byte[1024*1024*10]; protected void 
finalize() { System.out.println("I am released"); } } 執行結果 begin try to release content data request gc end 注意我們嘗試在@3@4處,釋放對tlcontent的引用,以便JAVA虛擬機器回收content。但是測試結果表明還有對content的引用,以致它沒有能被JAVA虛擬機器回收。 我們必須把@1@2處的程式碼開啟,才能把讓它讓JAVA虛擬機器回收content.推薦開啟@2而不是@1 @1@2處的程式碼開啟後的執行結果如下: begin try to release content data request gc I am released end 另外注意,@3其實並不影響執行結果。 事實上每個Thread例項都有一個ThreadLocalMap成員變數,它以ThreadLocal物件為key,以ThreadLocal繫結的物件為Value .我們呼叫ThreadLocal的set()方法,只是把要繫結的物件存放在當前執行緒的ThreadLocalMap成員變數中,以便下次通過get()方法取得它。 ThreadLocalMap和普通map的最大區別就是它的Entry是針對ThreadLocal弱引用的,即當ThreadLocal沒有其他引用為空時,JVM就可以GC回收ThreadLocal,從而得到一個null的key。 ThreadlocalMap維護了ThreadLocal物件和其繫結物件之間的關係,這個ThreadLocalMap有threshold,當超過threshold時, ThreadLocalMap會首先檢查內部ThreadLocal引用(前文說過,ThreadLocal是弱引用可以釋放)是否為null,如果存在null,那麼把繫結物件的引用設定為null,以便釋放ThreadLocal繫結的物件,這樣就騰出了位置給新的ThreadLocal。如果不存在slate threadlocal,那麼double threshold。 除此之外,還有兩個機會釋放掉已經廢棄的ThreadLocal繫結的物件所佔用的記憶體, 一、當hash演算法得到的table index剛好是一個null 的key的threadlocal時,直接用新的ThreadLocal替換掉已經廢棄的。 二、每次在ThreadLocalMap中存放ThreadLocal,hash演算法沒有命中既有Entry,需要新建一個Entry時,也呼叫cleanSomeSlots來遍歷清理Entry陣列中已經廢棄的ThreadLocal繫結的物件的引用 此外,Thread本身銷燬時,這個ThreadLocalMap也一定被銷燬了(ThreadLocalMap是Thread物件的成員), 這樣所有繫結到該執行緒的ThreadLocal的Object Value物件,如果在外部沒被引用的話(通常是這樣),也就沒有任何引用繼續保持,所以也就被銷燬回收了。 從上可以看出Java已經充分考慮了時間和空間的權衡,但是因為置為null的ThreadLocal對應的Object Value在無外部引用時,任然無法及時回收。 ThreadLocalMap只有到達threshold時或新增entry時才做檢查,不似gc是定時檢查, 不過我們可以手工通過ThreadLocal的remove()方法或set(null)解除ThreadLocalMap對ThreadLocal繫結物件的引用,及時的清理廢棄的threadlocal繫結物件的記憶體以。remove()往往還能做更多的清理工作,因此推薦使用它,而不使用set(null). 需要說明的是,只要不往不用的threadlocal中放入大量資料,問題不大,畢竟還有回收的機制。 被廢棄了的ThreadLocal所繫結物件的引用,會在以下4情況被清理。 如果此時外部沒有繫結物件的引用,則該繫結物件就能被回收了: 1 Thread結束時。 2 當Thread的ThreadLocalMap的threshold超過最大值時。 3 向Thread的ThreadLocalMap中存放一個ThreadLocal,hash演算法沒有命中既有Entry,而需要新建一個Entry時。 4 手工通過ThreadLocalremove()方法或set(null)。 因此如果我們粗暴的把ThreadLocal設定null,而不呼叫remove()方法或set(null),那麼就可能造成ThreadLocal繫結的物件長期也能被回收,因而產出記憶體洩露。