關於ThreadLocal記憶體洩漏引起的思考
概述
最近在對一個專案進行重構,用到了ThreadLocal。場景如下:外圍系統會呼叫介面上傳資料,在介面中要記錄資料的變化Id,在上傳資料完後需要集中在一個地方把這些Id以訊息形式傳送出去。
使用場景樣例程式碼
public Result<Void> uploadOrder(TotalPayInfoVo totalPayInfoVo) {
try {
saveTotalPayInfoVo(totalPayInfoVo);
//傳送訊息
UnitWork.getCurrent().pushMessage();
} catch (Exception e) {
cashLogger.error("uploadOrder error,data: {}, error: {}", JSON.toJSONString(totalPayInfoVo), e);
throw new RuntimeException("儲存失敗", e);
} finally {
UnitWork.clean();//
}
return ResultUtil.successResult();避免記憶體洩漏
}
ThreadLocal使用原始碼
/**
* 工作單元,在同一個執行緒中負責記錄一個事件或者一個方法或者一個事務過程中產生的變化,等操作結束後再處理這種變化。
*/
public class UnitWork {
private UnitWork() {
}
private static ThreadLocal<UnitWork> current = new ThreadLocal<UnitWork>() {
protected UnitWork initialValue() {
return new UnitWork();
}
};
/**
* 狀態變化的instance
*/
private Set<String> statusChangedInstances = new HashSet<>();
public void addStatusChangedInstance(String instance) {
statusChangedInstances.add(instance);
}
/**
* 推送訊息
*/
public void pushMessage() {
for(String id : statusChangedInstances){
//非同步發訊息
}
}
public static UnitWork getCurrent() {
return current.get();
}
/**
* 刪除當前執行緒的工作單元,建議放在finally中呼叫,避免記憶體洩漏
*/
public static void clean() {
current.remove();
}
}
思考問題
為了避免記憶體洩漏,每次用完做一下clean清理操作。傳送訊息的過程是非同步的,意味著clean的時候可能和傳送訊息同時進行。那麼會不會把這些Id清理掉?那麼可能造成訊息傳送少了。要回答這個問題,首先要搞懂ThreadLocal的引用關係,remove操作做了什麼?
ThreadLocal解讀
ThreadLocal可以分別在各個執行緒儲存變數獨立副本。每個執行緒都有ThreadLocalMap,顧名思義,類似Map容器,不過是用陣列Entry[]來模擬的。那麼既然類似Map,肯定會存在Key。其實Key是ThreadLocal型別,Key的值是ThreadLocal的HashCode,即通過threadLocalHashCode計算出來的值。
這個Map的Entry並不是ThreadLocal,而是一個帶有弱引用的Entry。既然是弱引用,每次GC的時候都會回收。
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
而Key對應的value就是要儲存線上程副本Object,這裡指的就是UnitWork的例項。呼叫ThreadLocal的get方法時,首先找到當前執行緒的ThreadLocalMap,然後根據這個ThreadLocal算出來的hashCode找到儲存執行緒副本Object。他們的關係對應如下:
ThreadLocal在remove的時候,會呼叫Entry的clear,即弱引用的clear方法。把Key->ThreadLocal的引用去掉。接下來的expungeStaleEntry會把entry中value引用設定為null。
/**
* Remove the entry for key.
*/
private void remove(ThreadLocal key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
現在可以回答之前提前的問題。雖然ThreadLocal和當前執行緒都會與Object脫離了引用的關係,但是最重要一點就是非同步的執行緒仍然存在一條強引用路徑到Object,即到UnitWork例項的強引用。因此GC然後不會回收UnitWork的例項,發訊息還是不會少發或者出現空指標情況。
相關推薦
關於ThreadLocal記憶體洩漏引起的思考
概述 最近在對一個專案進行重構,用到了ThreadLocal。場景如下:外圍系統會呼叫介面上傳資料,在介面中要記錄資料的變化Id,在上傳資料完後需要集中在一個地方把這些Id以訊息形式傳送出去。 使用場景樣例程式碼 public Result
ThreadLocal記憶體洩漏
前言 ThreadLocal 的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度。但是如果濫用ThreadLocal,就可能會導致記憶體洩漏。下面,我們將圍繞三個方面來分析ThreadLocal 
ThreadLocal記憶體洩漏真因探究
ThreadLocal原理回顧 ThreadLocal的原理:每個Thread內部維護著一個ThreadLocalMap,它是一個Map。這個對映表的Key是一個弱引用,其實就是ThreadLocal本身,Value是真正存的執行緒變數Object。 也就是說Thre
深入分析 ThreadLocal 記憶體洩漏問題
前言 ThreadLocal 的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度。但是如果濫用ThreadLocal,就可能會導致記憶體洩漏。下面,我們將圍繞三個方面來分析ThreadLoca
ThreadLocal記憶體洩漏問題
ThreadLocal實現變數的訪問隔離原理是在每個執行緒的內部維護了一個ThreadLocalMap型別的變數,這個變數的key就是該ThreadLocal(弱引用型別),值就是每個執行緒儲存的值。從ThreadLocal獲取值的時候,是先獲取當前執行的執行緒,從而獲取到當
什麼,你的ThreadLocal記憶體洩漏了?
前言 又是一個風和日立的早上,這天小美遇到了一個難題:
對 精緻碼農大佬 說的 Task.Run 會存在 記憶體洩漏 的思考
## 一:背景 ### 1. 講故事 這段時間專案延期,加班比較厲害,部落格就稍微停了停,不過還是得持續的技術輸出呀! 園子裡最近挺熱鬧的,精緻碼農大佬分享了三篇文章: * 為什麼要小心使用 Task.Run [https://www.cnblogs.com/willick/p/1407825
關於ThreadLocal引起記憶體洩漏的理解
ThreadLocal 的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度。但是如果濫用 ThreadLocal,就可能會導致記憶體洩漏。下面,我們將圍繞三個方面來分析 ThreadLo
threadlocal與ThreadPoolExecutor造成的記憶體洩漏
threadlocal與執行緒相關,每個執行緒都會有一份,參考 http://python.jobbole.com/86150/ ThreadPoolExecutor建構函式裡面有max_workers引數,如果這個引數設定的不好,就有可能造成記憶體洩漏。 示例程式碼如
單例模式引起的記憶體洩漏
單例模式是我們專案中經常使用的一個設計模式,但是如果使用不當,也會引發記憶體洩漏。 例如: 下面這種常見的寫法,傳了一個Context 進去 import android.content.Context; public class Utils { private Context
ThreadLocal原理、使用場景及存在記憶體洩漏的原因
什麼是執行緒封閉 當多執行緒訪問共享變數時,往往需要加鎖來保證共享變數的執行緒安全(資料同步)。一種避免使用加鎖方式就是不共享資料,而是讓執行緒獨享資料。由於資料本身就是執行緒私有的,這樣,如果僅在單執行緒內訪問資料就不需要同步,這種避免共享資料的技術稱為執行緒封閉。在Java語言中,提供了一些
Android開發-Handler引起的記憶體洩漏-實驗、分析、總結。
介紹 最近在惡補Handler的知識,其中就涉及到了Handler引起的記憶體洩露問題,網路上有很多的分析文章。我就按照這些文章的思路,寫程式碼驗證,主要是驗證和記錄。 使用的記憶體檢測工具是:LeakCanary 中文使用說明 英文原文: http://www
ThreadLocal為啥會出現記憶體洩漏
前言 ThreadLocal 的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度。但是如果濫用ThreadLocal,就可能會導致記憶體洩漏。下面,我們將圍繞四個方面來分析ThreadLocal 記憶體洩
高併發和ThreadLocal以及記憶體洩漏
併發程式設計 首先感謝https://blog.csdn.net/iter_zc/article/details/39546405這個系列的作者。之前接觸過高併發,但是都是在斷斷續續的接觸和學習,沒有一個結構化的學習。我的博文就是在看了他的講解之後自己理解的。如果我寫的不夠好,大家可以去
spring boot 引起的 “堆外記憶體洩漏”
spring boot 引起的 “堆外記憶體洩漏” 背景 組內一個專案最近一直報swap區域使用過高異常,筆者被叫去幫忙檢視原因。 發現配置的4G堆內記憶體,但是實際使用的實體記憶體高達7G,確實有點不正常,JVM引數配置是“-XX:MetaspaceSize=2
ThreadLocal實現原理和記憶體洩漏問題
1.概述 ThreadLocal不是為了解決多執行緒訪問共享變數,而是為每個執行緒建立一個單獨的變數副本,變數在多執行緒環境下訪問(通過get或set方法訪問)時能保證各個執行緒裡的變數相對獨立於其他執行緒內的變數,ThreadLocal例項通常來說都是private static型別。
js垃圾回收機制和引起記憶體洩漏的操作
JS的垃圾回收機制瞭解嗎? Js具有自動垃圾回收機制。垃圾收集器會按照固定的時間間隔週期性的執行。 JS中最常見的垃圾回收方式是標記清除。 工作原理:是當變數進入環境時,將這個變數標記為“進入環境”。當變數離開環境時,則將其標記為“離開環境”。標記
ThreadLocal 與記憶體洩漏
【Q】為什麼不能使用強引用? 先看看使用強引用會出現什麼問題。 ThreadLocalMap 中,value 作為一個本地變數,應該不會說總在使用,因此用完之後最好清理這個 Entry。 如果執行緒執行很快,執行緒退出後 ThreadLocalMap 不存在了自然不用再管其內部屬性了;但如果是長時間存在的執
ThreadLocal使用注意:執行緒不安全,可能會發生記憶體洩漏
先說可能會發生記憶體洩漏: 前言 ThreadLocal 的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,減少同一個執行緒內多個函式或者元件之間一些公共變數的傳遞的複雜度。但是如果濫用ThreadLocal,就可能會導致記憶體洩漏。下面,我們將圍繞三個
詳解ThreadLocal為何存在記憶體洩漏
ThreadLocal是Java中用於保證執行緒安全的一種措施,通過給每個執行緒分配一個專屬的值儲存空間,保證執行緒各自維護自己的變數,從而不會發生併發訪問問題。 但是ThreadLocal是存在著記憶體洩漏風險的,如果使用不當,容易發生memory leak錯誤。 首先解釋什麼是記憶體洩漏。