1 先上一張經(jīng)典的關(guān)系圖,完了貼下原始的代碼。
純看代碼寫的真是挺繞的,先來(lái)張圖有個(gè)整體的印象吧。

對(duì)著Thread類的源碼如下:每個(gè)Thread類都會(huì)有兩個(gè)這樣的Map變量:
/* 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;
這兩個(gè)變量是專門存儲(chǔ)Thread的局部變量用的。為啥整倆,有區(qū)別的,區(qū)別就再Thread的init方法里面,inheritableThreadLocals這個(gè)會(huì)繼承父線程的數(shù)據(jù)信息。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//上面的代碼省略。。。。。
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
ThreadLocal 大家寫的時(shí)候基本的用法都張這樣:
static final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();
private static final ThreadLocal<char[]> DEST_TL =
new ThreadLocal<char[]>() {
@Override
protected char[] initialValue() {
return new char[1024];
}
};
private final ThreadLocal<List<Object>> threadList;
ThreadLocal 的set方法張這樣
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal的解釋
ThreadLocal 就是用于線程(Thread)私有(Local)的存儲(chǔ)結(jié)構(gòu),這種結(jié)構(gòu)能夠使得線程能夠使用只有自己能夠訪問和修改的變量,從而實(shí)現(xiàn)多個(gè)線程之間的資源互相隔離,達(dá)到安全并發(fā)的目的。
通過上面的代碼,可以理解為我們給Thread搞些局部變量的時(shí)候,其實(shí)是通過ThreadLocal去操作Thread.ThreadLocalMap的。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類,是一個(gè)map結(jié)構(gòu),key是WeakReference<ThreadLocal<?>類型,value是object。注意這個(gè)key是WeakReference類型的。
WeakReference類型是什么意思呢?
搞段代碼實(shí)驗(yàn)下:
//---------------------------------------------------------
public class Car {
private double price;
private String colour;
public Car(double price, String colour){
this.price = price;
this.colour = colour;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getColour() {
return colour;
}
public void setColour(String colour) {
this.colour = colour;
}
public String toString(){
return colour +"car costs $"+price;
}
}
//---------------------------------------------------------
public class TestWeakReference {
public static void main(String[] args) {
Car car = new Car(22000,"silver");
WeakReference<Car> weakCar = new WeakReference<Car>(car);
int i=0;
while(true){
if(weakCar.get()!=null){
i++;
System.out.println("Object is alive for "+i+" loops - "+weakCar);
}else{
System.out.println("Object has been collected.");
break;
}
}
}
}
//-----------------------運(yùn)行結(jié)果如下:
.......
Object is alive for 85254 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85255 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85256 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85257 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85258 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85259 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85260 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85261 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85262 loops - java.lang.ref.WeakReference@46fbb2c1
Object is alive for 85263 loops - java.lang.ref.WeakReference@46fbb2c1
Object has been collected.
WeakReference對(duì)象的特性就是代碼運(yùn)行一段時(shí)間,如果碰到gc,WeakReference指向的對(duì)象會(huì)被gc回收。
WeakReference的一個(gè)特點(diǎn)是它何時(shí)被回收是不可確定的, 因?yàn)檫@是由GC運(yùn)行的不確定性所確定的. 所以, 一般用weak reference引用的對(duì)象是有價(jià)值被cache, 而且很容易被重新被構(gòu)建, 且很消耗內(nèi)存的對(duì)象.
#另一種常用的引用類型介紹:SoftReference
soft reference和weak reference一樣, 但被GC回收的時(shí)候需要多一個(gè)條件: 當(dāng)系統(tǒng)內(nèi)存不足時(shí), soft reference指向的object才會(huì)被
回收. 正因?yàn)橛羞@個(gè)特性, soft reference比weak reference更加適合做cache objects的reference. 因?yàn)樗梢员M可能的retain
cached objects, 減少重建他們所需的時(shí)間和消耗.
ThreadLocalMap的key為啥要設(shè)置成WeakReference類型呢?
就是為了防止內(nèi)存泄漏,讓gc回收,沒別的。 value也設(shè)置成WeakReference豈不更好,感覺上畢竟value是從外面?zhèn)魅氲?,萬(wàn)一被別人引用了呢。純猜的。
并且ThreadLocalMap源碼里面有個(gè)這個(gè)方法expungeStaleEntry(),這個(gè)方法的目的就是將key為null的Entry的velue設(shè)置為null,在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)直接或者間接調(diào)用這個(gè)方法。相當(dāng)于加了個(gè)保險(xiǎn),如果key被gc回收后,那你只要有操作map,我就給你做清楚操作。相當(dāng)于為內(nèi)存泄漏又加了個(gè)保險(xiǎn)。
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
有個(gè)哥們兒總結(jié)的挺牛逼:
從表面上看內(nèi)存泄漏的根源在于使用了弱引用。網(wǎng)上的文章大多著重分析ThreadLocal使用了弱引用會(huì)導(dǎo)致內(nèi)存泄漏,但是另一個(gè)問題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用?
我們先來(lái)看看官方文檔的說法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了幫助處理非常大且長(zhǎng)期使用的用法,哈希表?xiàng)l目使用WeakReferences作為鍵。
下面我們分兩種情況討論:
key 使用強(qiáng)引用:引用的ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒有手動(dòng)刪除,ThreadLocal不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。
key 使用弱引用:引用的ThreadLocal的對(duì)象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動(dòng)刪除,ThreadLocal也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。
比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果都沒有手動(dòng)刪除對(duì)應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏,對(duì)應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。
因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng)key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>
什么情況下內(nèi)存會(huì)泄漏呢?
假設(shè)你用的是線程池,那池子里面的線程自始至終都是活的,線程不被銷毀,并且你用完后就沒怎么操作過這個(gè)ThreadLocal,key雖然會(huì)在gc時(shí)被回收,value一直被ThreadLocalMap引用著,可能會(huì)造成value的累積。
個(gè)人感覺
大家都說ThreadLocal用的時(shí)候會(huì)造成內(nèi)存泄漏,原因呢大部分歸結(jié)到弱引用,完了巴拉巴拉,說的人一頭霧水。給我的感覺哈其實(shí)ThreadLocalMap用了WeakReferences做為鍵,并且在set,get,remove里面清除value,恰恰是未了防止內(nèi)存泄漏。他寫的時(shí)候想法很好,我把key設(shè)置WeakReferences,gc的時(shí)候就回收了,完了你再操作ThreadLocal的時(shí)候呢,我把value也幫著你刪了。泄漏的原因其實(shí)就是你不會(huì)用。。。。。哈哈
解決方案
記得用完remove了 兄弟。
他們寫的都挺牛逼,可以拜讀下:
關(guān)于ThreadLocal內(nèi)存泄露的備忘
深入分析 ThreadLocal 內(nèi)存泄漏問題