欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于ThreadLocal使用時(shí)OOM的討論

 更新時(shí)間:2025年06月29日 13:40:01   作者:找不到、了  
這篇文章主要介紹了關(guān)于ThreadLocal使用時(shí)OOM的討論,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

之前介紹Spring bean線(xiàn)程安全的問(wèn)題時(shí)候,討論到 ThreadLocal 類(lèi)提供了線(xiàn)程局部變量,每個(gè)線(xiàn)程可以將一個(gè)值存在 ThreadLocal 對(duì)象中,其他線(xiàn)程無(wú)法訪(fǎng)問(wèn)這些值。每個(gè)線(xiàn)程都有自己獨(dú)立的變量副本。

但如果使用不當(dāng),它可能會(huì)導(dǎo)致 內(nèi)存泄漏(Memory Leak),最終引發(fā) (OOM)。根本原因在于 ThreadLocal 的存儲(chǔ)機(jī)制 和 垃圾回收(GC)行為。

1、數(shù)據(jù)結(jié)構(gòu)

位于java.lang包下面。

1.1、內(nèi)存存儲(chǔ)結(jié)構(gòu)

ThreadLocal 的核心存儲(chǔ)依賴(lài)于:

  • ThreadLocalMap(每個(gè) Thread 內(nèi)部維護(hù)的一個(gè)類(lèi)似 WeakHashMap 的結(jié)構(gòu))
  • EntryThreadLocalMap 的存儲(chǔ)單元,key 是 ThreadLocal 本身,value 是存儲(chǔ)的值)

如下圖所示:

定義時(shí)候,可參考如下:

ThreadLocal.ThreadLocalMap threadLocals; // 每個(gè)線(xiàn)程的 ThreadLocal 數(shù)據(jù)存儲(chǔ)在這里

ThreadLocalMap 的 Entry 是 弱引用(WeakReference) 的:

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value; // 存儲(chǔ)的值是強(qiáng)引用
    Entry(ThreadLocal<?> k, Object v) {
        super(k); // key(ThreadLocal)是弱引用
        value = v; // value 是強(qiáng)引用
    }
}

而對(duì)于key是弱引用,value是強(qiáng)引用。

2. 內(nèi)存泄漏

如下圖所示:

        ThreadLocal 的內(nèi)存泄漏問(wèn)題主要發(fā)生在 線(xiàn)程池環(huán)境(如 Tomcat、Spring 的異步任務(wù)等),因?yàn)榫€(xiàn)程會(huì)被復(fù)用,導(dǎo)致 ThreadLocalMap 長(zhǎng)期存活。

2.1、引用回收

key(ThreadLocal)是弱引用

  • 如果 ThreadLocal 對(duì)象沒(méi)有外部強(qiáng)引用(比如 static 修飾),它會(huì)被 GC 回收,Entry 的 key 變成 null。

value 是強(qiáng)引用

  • 即使 key 被回收,value 仍然被 ThreadLocalMap 強(qiáng)引用,無(wú)法被 GC 回收。

2.2、value的強(qiáng)引用目的

1、如果是弱引用,調(diào)用get方法,返回為null,value 可能被提前回收,導(dǎo)致數(shù)據(jù)丟失。

2、設(shè)計(jì)目標(biāo)是 讓每個(gè)線(xiàn)程可以安全地存儲(chǔ)自己的數(shù)據(jù),而不是讓數(shù)據(jù)隨時(shí)可能被回收。如果 value 是弱引用,就失去了存儲(chǔ)數(shù)據(jù)的可靠性。

2.3、線(xiàn)程長(zhǎng)期存活

如果線(xiàn)程是線(xiàn)程池中的(如 Tomcat 的工作線(xiàn)程),線(xiàn)程不會(huì)銷(xiāo)毀,ThreadLocalMap 會(huì)一直存在。

如果 ThreadLocal 使用后沒(méi)有 remove()value 會(huì)一直占用內(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()!
}

問(wèn)題

  • 每次 HTTP 請(qǐng)求結(jié)束后,Tomcat 線(xiàn)程不會(huì)銷(xiāo)毀,而是放回線(xiàn)程池。
  • 如果 User 對(duì)象很大,多次請(qǐng)求后,ThreadLocalMap 會(huì)積累大量 User 對(duì)象,最終 OOM。

小結(jié)

3、處理方案

先根據(jù)數(shù)據(jù)結(jié)構(gòu)進(jìn)行分析,如下圖所示:

3.1、remove

try {
    UserContextHolder.set(user);
    // ...業(yè)務(wù)邏輯
} finally {
    UserContextHolder.remove(); // 必須清理!
}

最佳實(shí)踐:在 finally 塊中調(diào)用 remove(),確保即使發(fā)生異常也能清理。

3.2、static修飾

private static final ThreadLocal<User> userHolder = new ThreadLocal<>();

原因:防止 ThreadLocal 被意外回收(弱引用失效)。

3.3、避免存儲(chǔ)大對(duì)象

如果 ThreadLocal 存儲(chǔ)的是大對(duì)象(如緩存、Session 數(shù)據(jù)),考慮改用其他方式(如 Redis)。

3.4、InheritableThreadLocal

InheritableThreadLocal 會(huì)傳遞給子線(xiàn)程,如果子線(xiàn)程不清理,同樣會(huì)導(dǎo)致內(nèi)存泄漏。

小結(jié)

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論