強(qiáng)引用與弱引用
強(qiáng)引用陶耍,使用最普遍的引用奋蔚,一個(gè)對象具有強(qiáng)引用,不會被垃圾回收器回收烈钞。當(dāng)內(nèi)存空間不足泊碑,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止毯欣,也不回收這種對象馒过。
如果想取消強(qiáng)引用和某個(gè)對象之間的關(guān)聯(lián),可以顯式地將引用賦值為null酗钞,這樣可以使JVM在合適的時(shí)間就會回收該對象腹忽。
弱引用来累,JVM進(jìn)行垃圾回收時(shí),無論內(nèi)存是否充足窘奏,都會回收被弱引用關(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ī)就判斷是可回收對象冷溃。
ps:GC 回收的是沒有被應(yīng)用的對象所占的空間(關(guān)鍵字空間)
部分重要的源碼
get()方法調(diào)用ThreadLocalMap的getEntry();
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 關(guān)鍵步驟
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
// 對于key == null的處理钱磅。
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
清理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;
}
ThreadLocalMap的remove()分析
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;
}
}
}
根據(jù)源碼我們假設(shè)如下場景:
1褪迟、key=ThreadLocal是強(qiáng)引用
2、key = ThreadLocal是弱引用
總結(jié)
無論 ThreadLocalMap 中的 key 使用哪種類型引用都無法完全避免內(nèi)存泄漏答憔,跟使用弱引用沒有關(guān)系味赃。
要避免內(nèi)存泄漏有兩種方式:
1 .使用完 ThreadLocal ,調(diào)用其 remove 方法刪除對應(yīng)的 Entry
2 .使用完 ThreadLocal 虐拓,當(dāng)前 Thread 也隨之運(yùn)行結(jié)束
相對第一種方式心俗,第二種方式顯然更不好控制,特別是使用線程池的時(shí)候蓉驹,線程結(jié)束是不會銷毀的.
也就是說城榛,只要記得在使用完ThreadLocal 及時(shí)的調(diào)用 remove ,無論 key 是強(qiáng)引用還是弱引用都不會有問題.
由于Thread中包含變量ThreadLocalMap态兴,因此ThreadLocalMap與Thread的生命周期是一樣長吠谢,如果都沒有手動刪除對應(yīng)key,都會導(dǎo)致內(nèi)存泄漏诗茎。
但是使用弱引用可以多一層保障:弱引用ThreadLocal不會內(nèi)存泄漏工坊,對應(yīng)的value在下一次ThreadLocalMap調(diào)用set(),get(),remove()的時(shí)候會被清除献汗。
ThreadLocal正確的使用方法
- 1 每次使用完ThreadLocal都調(diào)用它的remove()方法清除數(shù)據(jù)
- 2 將ThreadLocal變量定義成private static,這樣就一直存在ThreadLocal的強(qiáng)引用王污,也就能保證任何時(shí)候都能通過ThreadLocal的弱引用訪問到Entry的value值罢吃,進(jìn)而清除掉 。