-
什么是內存泄漏
- 程序在申請內存后,無法釋放已申請的內存空間
- 在定義變量時,需要一段內存空間來存儲數據信息,而這段內存如果一直不被釋放,那么就會導致內存被占用光,而被占用的這個對象,一直不能被回收掉,這就是內存泄漏
-
ThreadLocal
-
private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}
-
每一個ThreadLocal維護一個ThreadLocalMap,key為使用弱引用的ThreadLocal實例,value為線程變量的副本
-
-
強引用
- 使用最普遍的引用,一個對象具有強引用,不會被垃圾回收器回收,當內存空間不足,JAVA虛擬機寧愿拋出OOM錯誤,使程序異常終止,也不會受這種對象
- 如果想取消強引用和某個對象之間的關聯,可以顯示地將引用賦值為null,這樣可以使JVM在合適的時間就會回收該對象
-
弱引用
- JVM進行垃圾回收時,無論是內存是否充足,都會回收被弱引用關聯的對象,在java中,用java.lang.ref.WeakReference類來表示,可以在緩存中使用弱引用
-
內存泄漏
- ThreadLocalMap使用ThreadLocal的弱應用作為key,如果一個ThreadLocal不存在外部強引用,Key勢必會被GC回收,這樣就導致ThreadLocal中key為null,而value還存在著強引用,只有Thread線程退出以后,value的強引用鏈條才會斷掉
- 如果當前線程遲遲不結束,這些key為null的Entry的value就會一直存在一條強引用鏈
- Thread Ref -> Thread -> ThreadLocalMap ->Entry -> value
- 這個時候,永遠無法回收,就會造成ThreadLocal出現內存泄漏的問題
-
如果ThreadLocalMap使用ThreadLocal的強引用
- 因為ThreadLocalMap還持有ThreadLocal的強引用,如果沒有手動刪除,ThreadLocal不會被回收,導致Entry內存泄漏
- 當ThreadLocalMap的key為弱引用回收ThreadLocal時,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒有手動刪除,ThreadLocal也會被回收,當key為null,在下一次ThreadLocalMap調用set和get,remove方法時會被清除value的值
-
為什么使用弱引用
-
因為使用弱引用可以多一層保障,弱引用ThreadLocal不會內存泄漏,對應的value在下一次ThreadLocalMap調用set,get,remove時會被清除
-
因此,ThreadLocal內存泄漏的根本原因是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應的key就會導致內存泄漏,而不是因為弱引用
-
static class ThreadLocalMap {//hreadLocalMap中數據是存儲在Entry類型數組的table中的,Entry繼承了WeakReference(弱引用)static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}**成員變量** //初始容量 private static final int INITIAL_CAPACITY = 16;//ThreadLocalMap數據真正存儲在table中 private Entry[] table;//ThreadLocalMap條數 private int size = 0;//達到這個大小,則擴容 private int threshold;
-
構造函數
-
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//初始化table數組,INITIAL_CAPACITY默認值為16table = new Entry[INITIAL_CAPACITY];//key和16取得哈希值int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//創建節點,設置key-valuetable[i] = new Entry(firstKey, firstValue);size = 1;//設置擴容閾值setThreshold(INITIAL_CAPACITY); }
-
-
remove方法
-
private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);//如果threadLocalHashCode計算出的下標找到的key和傳入key不同,則證明出現哈希沖突,則循環向下查找for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {//如果key相同if (e.get() == key) {//刪除當前Entrye.clear();//清理expungeStaleEntry(i);return;}}}
-
-
-
解決方法
- 每次使用完ThreadLocal都調用他的remove方法清除數據
- 將ThreadLocal變量定義為private static,這樣就一直存在ThreadLocal的強引用,也就是保證任何時候都能通過ThreadLocal的弱引用訪問到Entry的value值,進而清除掉