-
強參考:常常 new 出來的物件就是強參考型別,只要強參考存在,垃圾回收器將永遠不會回收被參考的物件,哪怕記憶體不足的時候
-
軟參考:使用 SoftReference 修飾的物件被稱為軟參考,軟參考指向的物件在記憶體要溢位的時候被回收
-
弱參考:使用 WeakReference 修飾的物件被稱為弱參考,只要發生垃圾回收,若這個物件只被弱參考指向,那么就會被回收
-
虛參考:虛參考是最弱的參考,在 Java 中使用 PhantomReference 進行定義,虛參考中唯一的作用就是用佇列接收物件即將死亡的通知
1、ThreadLocal:執行緒的變數副本,每個執行緒 隔離
ThreadLocal物件可以提供執行緒區域變數,每個執行緒Thread擁有一份自己的副本變數,多個執行緒互不干擾,
多個執行緒操作這個變數時,實際是操作自己本地記憶體里的變數,從而起到執行緒隔離的作用,避免了執行緒安全問題,
2、ThreadLocal資料結構

(1)
==Thread類中存在ThreadLocal.ThreadLocalMap型別的實體變數ThreadLocal
==== 每個執行緒都有自己的ThreadLocalMap,ThreadLocalMap都有自己的獨立實作
========key:ThreadLocal(實際上不是ThreadLocal本身,而是ThreadLocal的一個弱參考);key是ThreadLocal<?> k,繼承自WeakReference(弱參考型別)
========value:代碼中放入的值
====執行緒的寫讀
========每個執行緒在往ThreadLocal中寫入值時,是存入自己的ThreadLocalMap
========在讀取值是,是以ThreadLocal為參考,在自己的ThreadLocalMap中查找對應的key:ThreadLocal,對應的value,從而達到執行緒隔離(寫入讀取本身的ThreadLocalMap)
(2)區別于HashMap:
==HashMap是由陣列+鏈表實作的
==ThreadLocalMap是沒有鏈表結構,內部維護了Entry陣列,每個Entry代表一個完整的物件,
3、ThreadLocal.set()原理

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
?
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
(1)獲取Thread中的threadLocals
(2)判斷對應的ThreadLocalMap是否存在
==存在,就往ThreadLocalMap中set資料,key為ThreadLocal,value為要set的值
====若經過hash計算,對應的Map的位置的Entry為null,則直接將Entry放入
====若經過hash計算,對應的Map的位置有與當前的ThreadLocal hash計算后的key值相同的Entry,則直接更新該Entry的資料
====若經過hash計算,對應的Map的位置有Entry了,則線性向后遍歷,直到遍歷到Entry為null的位置,直接將Entry放入
====若經過hash計算,對應的Map的位置不為空,線性向后遍歷時,遇到key為null(key過期)的Entry(即已被垃圾回收了)時,會執行replaceStaleEntry()方法,該方法是替換過期資料的邏輯,以當前key=null的Entry在Map中的下標index,staleSlot=slotToExpunge=index,開始遍歷,進行探測式資料清理作業,以當前下標開始,向前查找其它過期資料,有則更新slotToExpunge=當前過期資料的下標index_new,直到遇到Entry=null,(如果一直往前探測沒有遇到Entry=null,會從頭部掃描跳到最尾部繼續向前探測)然后更新開始查找的下標index為0,slotToExpunge=0;然后,從當前節點staleSlot(即第一次遇到的過期資料位置)開始,向后查找key值相等的Entry,找到后更新Entry的值,并交換下標為staleSlot的元素(過期元素)的位置,若在查找到key相等的Entry前遇到Entry為null的位置,就停止尋找,就會直接創建Entry(要set進入的Entry)替換staleSlot過期資料,然后開始進行過期Entry的清理作業.
==若不存在,就創建新的ThreadLocalMap并進行set
4、ThreadLocalMap的hash演算法
(1)ThreadLocalMap實作hash演算法來解決散串列陣列的hash沖突問題
int i = key.threadLocalHashCode & (len-1);
(2)threadLocalHashCode的計算
==ThreadLocal一個屬性:HASH_INCREMENT=0x61c88647
==每創建一個ThreadLocal物件,ThreadLocal.nextHashCode增長0x61c88647
==0x61c88647:斐波那契數,ThreadLocalMap的hash增量,hash分布非常均勻
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
?
private static AtomicInteger nextHashCode = new AtomicInteger();
?
private static final int HASH_INCREMENT = 0x61c88647;
?
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
?
static class ThreadLocalMap {
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
?
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
5、ThreadLocalMap的hash沖突
(1)在插入程序中,通過hash計算
==若沒有沖突,則直接放入ThreadLocalMap中
==若發生hash沖突,則在沖突的位置后線性向后查找,一直找到Entry為null的位置即Map為空的位置,并將該Entry放入該位置
==若遇到Entry不為null且key相等的情況,直接更新該位置的資料
==若遇到Entry中key為null的情況
====[1]記錄當前過期資料位置index staleSlot=slotToExpunge=index;
====[2]從當前位置向前查找,每查找到過期資料,便更新slotToExpunge
slotToExpunge=index_new(每找到的過期資料下標)
====[3]不斷向前直到找到Entry=null(可能從頭跳到尾繼續查找),就停止查找,設定開始時的下標index為0 slotToExpunge為0
index=0 ;slotToExpunge=0
====[4]從第一次遇到的過期資料即下標為index(此時index=0),開始向后查找
若遇到Entry的key值與要插入的key值相等,則更新Entry的資料,并與下標index的Entry進行位置交換,并開始index的Entry的清理
若在遇到相等key值前遇到過期資料或Entry為空的情況下,將要插入的Entry創建,直接替換下標index的Entry
6、ThreadLocalMap過期key的清理
(1)探測式清理 expungeStaleEntry()
==程序:
[1]從開始位置向后探測清理過期資料,將過期資料Entry設定為null
[2]探測程序中遇到Entry不為null的資料reEntry,進行rehash(重哈希計算),重新在陣列中定位
[3]若rehash定位,定位到的位置有資料,則將資料reEntry放到最靠近這個位置的后邊的Entry=null的位置
[4]如果再有其它資料set到map中,會觸發探測式清理作業
[5]一直探測,直到遇到Entry=null,才停止探測
(2)啟發式清理 cleanSomeSlots() :清理散列陣列中Entry,key=null的資料,
==程序;
[1]如果在插入時遇到過期資料,下標index,此時slotToExpunge=stableSlot=index;
[2]從當前index向前開始查找其它過期資料時,直到找到Entry=null,也沒有找到過期資料時
[3]向后查找過期資料進行Entry交換或者Entry新建直接替代插入時,也沒有找到Entry=null或過期資料時
[4]此時,slotToExpunge==stableSlot==index,slotToExpunge不是等于0,則會呼叫cleanSomeSlots(expungeStaleEntry(slotToExpung),len),進行啟發式過期資料清理,即直接清理當前slotToExpung的過期資料,然后才插入Entry新建,
7、ThreadLocalMap擴容機制
(1)進行set()方法后,如果執行完啟發式清理作業后,沒有清理到任何資料,且當前散列陣列中Entry的數量已經達到了串列的擴容閾值(len*2/3),就會執行rehash()
rehash()的閾值是 size>=threshold
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
(2)rehash()
private void rehash() {
expungeStaleEntries();
?
if (size >= threshold - threshold / 4)
resize();
}
?
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)
expungeStaleEntry(j);
}
}
==[1]首先會進行探測式清理作業,從陣串列的起始位置開始往后清理
==[2]清理完成后,陣列可能有一些key=null的過期資料被清理掉,此時判斷
判斷size>=threshold-threshold/4 (true|false 來決定是否擴容)
(3)resize()
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
?
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
?
setThreshold(newLen);
size = count;
table = newTab;
}
==[1]創建新的擴容后的陣列,擴容后的陣列大小為 oldLen*2
==[2]遍歷老的散串列,重新計算hash位置,將資料放入新的tab陣列中
==[3]重新計算新表的閾值
8、ThreadLocalMap.get()
(1)set()方法:包括set資料、清理資料、優化資料表(rehash和擴容)
(2)
==第一種情況:
通過查找key,計算出散串列中slot位置,若該slot位置中的Entry.key=key,則直接回傳資料
==第二種情況:
[1]通過查找key ,計算出散串列中slot位置,若該slot位置中已經有了資料且Entry.key!=key,則需要繼續向后查找
[2]如果遇到過期資料key=null,觸發一次探測式資料回收操作,執行expungeStaleEntry()方法,清理過期資料,而后邊的未過期資料會rehash()前移
[3]繼續往后查找,直到遇到key值相等的Entry,則回傳資料
[4]若往后查找時,遇到Entry=null時,則停止查找,表示沒有該key對應的資料
(3)GC后key是否為空?
ThreadLocalMap的key,即ThreadLocal是弱參考,在ThreadLocal.get()時,發生GC后,key是否為null?
==一般情況下,GC后弱參考會被垃圾回收,key會等于null,會出現value沒被回收,key被回收,key=null,導致value一直存在,會出現記憶體泄漏OOM
==但此時,是ThreadLocal.get()操作,則說明有強參考存在,此時key還有對應的ThreadLocal,ThreadLocald的強參考還存在,則不會被垃圾回收,key不為null

9、ThreadLocal記憶體泄漏問題OOM
(1)ThreadLocalMap中key值 ThreadLocal是弱參考
(2)弱參考,GC后會被垃圾回收,
(3)如果ThreadLocal被GC垃圾回收,此時ThreadLocalMap的key=null
(4)ThreadLocalMap的生命周期跟Thread一樣的,此時,key==null,對應的value還存在,可能會造成記憶體泄漏問題OOM
(5)解決方法:使用完ThreadLocal后,及時呼叫remove()方法釋放記憶體空,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/458467.html
標籤:Java
上一篇:ReadWriteLock讀寫鎖
下一篇:Postman 正確使用姿勢
