ThreadLocal的記憶體洩露
阿新 • • 發佈:2019-01-01
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處,釋放對tl和content的引用,以便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 手工通過ThreadLocal的remove()方法或set(null)。
因此如果我們粗暴的把ThreadLocal設定null,而不呼叫remove()方法或set(null),那麼就可能造成ThreadLocal繫結的物件長期也能被回收,因而產出記憶體洩露。