關(guān)于ThreadLocal使用時OOM的討論
之前介紹Spring bean線程安全的問題時候,討論到 ThreadLocal 類提供了線程局部變量,每個線程可以將一個值存在 ThreadLocal 對象中,其他線程無法訪問這些值。每個線程都有自己獨立的變量副本。
但如果使用不當(dāng),它可能會導(dǎo)致 內(nèi)存泄漏(Memory Leak),最終引發(fā) (OOM)。根本原因在于 ThreadLocal 的存儲機制 和 垃圾回收(GC)行為。
1、數(shù)據(jù)結(jié)構(gòu)
位于java.lang包下面。
1.1、內(nèi)存存儲結(jié)構(gòu)
ThreadLocal 的核心存儲依賴于:
ThreadLocalMap(每個Thread內(nèi)部維護的一個類似WeakHashMap的結(jié)構(gòu))Entry(ThreadLocalMap的存儲單元,key是ThreadLocal本身,value是存儲的值)
如下圖所示:

定義時候,可參考如下:
ThreadLocal.ThreadLocalMap threadLocals; // 每個線程的 ThreadLocal 數(shù)據(jù)存儲在這里
ThreadLocalMap 的 Entry 是 弱引用(WeakReference) 的:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 存儲的值是強引用
Entry(ThreadLocal<?> k, Object v) {
super(k); // key(ThreadLocal)是弱引用
value = v; // value 是強引用
}
}而對于key是弱引用,value是強引用。
2. 內(nèi)存泄漏
如下圖所示:

ThreadLocal 的內(nèi)存泄漏問題主要發(fā)生在 線程池環(huán)境(如 Tomcat、Spring 的異步任務(wù)等),因為線程會被復(fù)用,導(dǎo)致 ThreadLocalMap 長期存活。
2.1、引用回收
key(ThreadLocal)是弱引用:
- 如果
ThreadLocal對象沒有外部強引用(比如static修飾),它會被 GC 回收,Entry的key變成null。
value 是強引用:
- 即使
key被回收,value仍然被ThreadLocalMap強引用,無法被 GC 回收。
2.2、value的強引用目的
1、如果是弱引用,調(diào)用get方法,返回為null,value 可能被提前回收,導(dǎo)致數(shù)據(jù)丟失。
2、設(shè)計目標(biāo)是 讓每個線程可以安全地存儲自己的數(shù)據(jù),而不是讓數(shù)據(jù)隨時可能被回收。如果 value 是弱引用,就失去了存儲數(shù)據(jù)的可靠性。
2.3、線程長期存活
如果線程是線程池中的(如 Tomcat 的工作線程),線程不會銷毀,ThreadLocalMap 會一直存在。
如果 ThreadLocal 使用后沒有 remove(),value 會一直占用內(nèi)存,最終導(dǎo)致 內(nèi)存泄漏。
示例如下:
public class UserContextHolder {
private static ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void set(User user) {
userHolder.set(user);
}
public static User get() {
return userHolder.get();
}
// 忘記調(diào)用 remove()!
}問題:
- 每次 HTTP 請求結(jié)束后,Tomcat 線程不會銷毀,而是放回線程池。
- 如果
User對象很大,多次請求后,ThreadLocalMap會積累大量User對象,最終 OOM。
小結(jié)

3、處理方案
先根據(jù)數(shù)據(jù)結(jié)構(gòu)進行分析,如下圖所示:

3.1、remove
try {
UserContextHolder.set(user);
// ...業(yè)務(wù)邏輯
} finally {
UserContextHolder.remove(); // 必須清理!
}最佳實踐:在 finally 塊中調(diào)用 remove(),確保即使發(fā)生異常也能清理。
3.2、static修飾
private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
原因:防止 ThreadLocal 被意外回收(弱引用失效)。
3.3、避免存儲大對象
如果 ThreadLocal 存儲的是大對象(如緩存、Session 數(shù)據(jù)),考慮改用其他方式(如 Redis)。
3.4、InheritableThreadLocal
InheritableThreadLocal 會傳遞給子線程,如果子線程不清理,同樣會導(dǎo)致內(nèi)存泄漏。
小結(jié)

總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
JavaWeb中struts2實現(xiàn)文件上傳下載功能實例解析
這篇文章主要介紹了JavaWeb中struts2文件上傳下載功能的實現(xiàn),在Web應(yīng)用系統(tǒng)開發(fā)中,文件上傳和下載功能是非常常用的功能,需要的朋友可以參考下2016-05-05
IDEA2023隱藏.idea和.iml文件的實現(xiàn)步驟
IDEA新建項目會自動生成一個.idea文件夾和.iml文件,本文主要介紹了IDEA2023隱藏.idea和.iml文件的實現(xiàn)步驟,具有一定的參考價值,感興趣的可以了解一下2023-09-09
零基礎(chǔ)入門學(xué)習(xí)——Spring Boot注解(一)
這篇文章主要介紹了Spring Boot注解學(xué)習(xí)(一)要點,非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-05-05
Java 實戰(zhàn)項目之教材管理系統(tǒng)的實現(xiàn)流程
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)教材管理系統(tǒng),大家可以在過程中查缺補漏,提升水平2021-11-11
Spring Cloud Feign性能優(yōu)化代碼實例
這篇文章主要介紹了Spring Cloud Feign性能優(yōu)化代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-03-03

