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

ThreadLocal導(dǎo)致JVM內(nèi)存泄漏原因探究

 更新時(shí)間:2023年04月19日 11:24:57   作者:胡尚  
ThreadLocal是JDK提供的線程本地變量機(jī)制,但若使用不當(dāng)可能導(dǎo)致內(nèi)存泄漏。正確的使用方式是在使用完后及時(shí)remove,或者使用弱引用等手段避免強(qiáng)引用導(dǎo)致的內(nèi)存泄漏。在多線程編程中,合理使用ThreadLocal可以提高并發(fā)性能,但也需要注意其潛在的內(nèi)存泄漏問(wèn)題

為什么要使用ThreadLocal

在一整個(gè)業(yè)務(wù)邏輯流程中,為了在不同的地方或者不同的方法中使用同一個(gè)對(duì)象,但是又不想在方法形參中加這個(gè)對(duì)象,那么就可以使用ThreadLocal來(lái)保存

ThreadLocal最大的應(yīng)用場(chǎng)景就是跨方法進(jìn)行參數(shù)傳遞

ThreadLocal可以給每一個(gè)線程綁定一個(gè)變量的副本

使用ThreadLocal

ThreadLocal常用的方法其實(shí)也就下面幾個(gè)

// 返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量。
public T get() {}
// 設(shè)置當(dāng)前線程的線程局部變量的值。
public void set(T value) {}
// 移除,當(dāng)線程結(jié)束后,該線程thread對(duì)象中的局部變量將在下一次gc時(shí)回收,如果顯示的調(diào)用此方法只是可以加快內(nèi)存回收的速度
// 所以javase開發(fā) 普通new Thread()方式中,這個(gè)方法并不是必須要調(diào)用的
// 但是javaWeb開發(fā)中就必須顯示調(diào)用,因?yàn)閖avaweb都是使用的線程池,并不是一個(gè)客戶端來(lái)一個(gè)請(qǐng)求,thread線程對(duì)象用完就刪除,而是會(huì)放回線程池中。
public void remove() {}
// 返回該線程局部變量的一個(gè)初始化
// protected方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。這個(gè)方法在第一次調(diào)用 get()或 set(Object)時(shí)才執(zhí)行,并且僅執(zhí)行 1 次
protected T initialValue() {}

在具體使用的時(shí)候,我們ThreadLocal對(duì)象一定會(huì)定義成靜態(tài)的,如果不定義成靜態(tài)的那么其他地方如何通過(guò)這個(gè)ThreadLocal實(shí)例去Map中拿數(shù)據(jù)嘞?

而且如果是多個(gè)線程保存一個(gè)變量的副本,一個(gè)靜態(tài)的ThreadLocal也足夠了,因?yàn)樗亲鳛槎鄠€(gè)map中的key存在的

簡(jiǎn)單使用案例

/**
 * @Description: 在一個(gè)方法中調(diào)用set()方法存值,在另一個(gè)方法中調(diào)用get()方法取值
 */
public class UseThreadLocalTest {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    /**
     * 創(chuàng)建一個(gè)線程類
     */
    public static class ThreadTest extends Thread{
        private Integer id;
        ThreadTest(Integer id){
            this.id = id;
        }
        @Override
        public void run() {
            threadLocal.set(Thread.currentThread().getName() + ":" + id);
            print();
        }
        public void print(){
            System.out.println(threadLocal.get());
        }
    }
    /**
     * 開三個(gè)線程
     */
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadTest(i).start();
        }
    }
}

// 輸入結(jié)果如下
Thread-0:0
Thread-1:1
Thread-2:2

具體實(shí)現(xiàn)

ThreadLocal底層set()和get()方法的源碼如下

// 存值時(shí) map最終是存儲(chǔ)在當(dāng)前線程Thread t = Thread.currentThread()中的,是thread的一個(gè)成員變量
// map的key是當(dāng)前threadLocal對(duì)象實(shí)例,value是要存的值
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
// 取值時(shí)也是也是先從當(dāng)前線程Thread對(duì)象中取出map
// 然后在從map中根據(jù)當(dāng)前threadLocal對(duì)象實(shí)例作為key獲取到entry對(duì)象
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

為了提高性能,才沒(méi)有采用加鎖的方式,而是將map和各個(gè)線程thread對(duì)象進(jìn)行關(guān)聯(lián),這樣就避免了產(chǎn)生線程安全問(wèn)題,也避免了加鎖,提高了性能

我們接下來(lái)再來(lái)看看ThreadLocalMap它的實(shí)現(xiàn),它類似于jdk1.7版本的hashmap,底層存儲(chǔ)的是一個(gè)Entry對(duì)象的數(shù)組,初始容量也是16,存值時(shí)先用hash結(jié)果和數(shù)組長(zhǎng)度取余得到數(shù)組下標(biāo)位置,然后判斷是否產(chǎn)生了hash沖突,然后使用開發(fā)定址法來(lái)處理。根據(jù)算法的不同又可以分為線性探測(cè)再散列、二次探測(cè)再散列、偽隨機(jī)探測(cè)再散列。ThreadLocalMap它是使用的線性探測(cè)再散列法,如下所示

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

Entry對(duì)象中的key它是一個(gè)弱引用,Entry繼承了WeakReference類,弱引用跟沒(méi)引用差不多,GC會(huì)直接回收掉,不管內(nèi)存是否足夠都會(huì)回收

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

引發(fā)內(nèi)存泄漏的原因

上面再介紹ThreadLocal基本使用api方法的時(shí)候也提到了,如果只是創(chuàng)建一個(gè)普通的線程Thread對(duì)象,是不會(huì)產(chǎn)生內(nèi)存泄漏問(wèn)題的。因?yàn)閙ap是存儲(chǔ)在Thread對(duì)象中,一個(gè)普通線程執(zhí)行完了,那么這個(gè)線程的局部變量也就會(huì)被gc回收。

但如果結(jié)合到了線程池,一個(gè)Thread線程對(duì)象用完后放回線程池中,如果這個(gè)時(shí)候我們程序不顯示的調(diào)用remove()方法,那么就會(huì)造成內(nèi)存泄漏問(wèn)題了。

因?yàn)镋ntry對(duì)象中的Key的弱引用,但是value還會(huì)存在,就會(huì)存在map中key為null的value

ThreadLocal 的底層實(shí)現(xiàn)中我們可以看見(jiàn),無(wú)論是 get()、set()在某些時(shí) 候,調(diào)用了 expungeStaleEntry() 方法用來(lái)清除 Entry 中 Key 為 null 的 Value,但是這是不及時(shí)的,也不是每次都會(huì)執(zhí)行的,所以一些情況下還是會(huì)發(fā)生內(nèi)存泄露。

到此這篇關(guān)于ThreadLocal導(dǎo)致JVM內(nèi)存泄漏原因探究的文章就介紹到這了,更多相關(guān)JVM內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot整合Mongodb實(shí)現(xiàn)增刪查改的方法

    SpringBoot整合Mongodb實(shí)現(xiàn)增刪查改的方法

    這篇文章主要介紹了SpringBoot整合Mongodb實(shí)現(xiàn)簡(jiǎn)單的增刪查改,MongoDB是一個(gè)以分布式數(shù)據(jù)庫(kù)為核心的數(shù)據(jù)庫(kù),因此高可用性、橫向擴(kuò)展和地理分布是內(nèi)置的,并且易于使用。況且,MongoDB是免費(fèi)的,開源的,感興趣的朋友跟隨小編一起看看吧
    2022-05-05
  • MyBatis通用的10種寫法總結(jié)大全

    MyBatis通用的10種寫法總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于MyBatis通用的10種寫法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • SpringBoot消息國(guó)際化配置實(shí)現(xiàn)過(guò)程解析

    SpringBoot消息國(guó)際化配置實(shí)現(xiàn)過(guò)程解析

    這篇文章主要介紹了SpringBoot消息國(guó)際化配置實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • SpringBoot 整合Redisson重寫cacheName支持多參數(shù)的案例代碼

    SpringBoot 整合Redisson重寫cacheName支持多參數(shù)的案例代碼

    這篇文章主要介紹了SpringBoot 整合Redisson重寫cacheName支持多參數(shù),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • JAVA基礎(chǔ)之注解與反射的使用方法和場(chǎng)景

    JAVA基礎(chǔ)之注解與反射的使用方法和場(chǎng)景

    這篇文章主要給大家介紹了關(guān)于JAVA基礎(chǔ)之注解與反射的使用方法和場(chǎng)景的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • JDK1.8、JDK1.7、JDK1.6區(qū)別看這里

    JDK1.8、JDK1.7、JDK1.6區(qū)別看這里

    這篇文章主要為大家詳細(xì)介紹了JDK1.8、JDK1.7、JDK1.6中的源碼,對(duì)比閱讀,發(fā)現(xiàn)修改問(wèn)題以及改進(jìn)點(diǎn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Java中Range函數(shù)的簡(jiǎn)單介紹

    Java中Range函數(shù)的簡(jiǎn)單介紹

    這篇文章主要介紹了Java中Range函數(shù)的簡(jiǎn)單介紹,Java中的range方法用于返回IntStream和LongStream在函數(shù)參數(shù)范圍內(nèi)的順序值
    2022-07-07
  • SpringMVC4+MyBatis+SQL Server2014實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫分離

    SpringMVC4+MyBatis+SQL Server2014實(shí)現(xiàn)數(shù)據(jù)庫(kù)讀寫分離

    這篇文章主要介紹了SpringMVC4+MyBatis+SQL Server2014實(shí)現(xiàn)讀寫分離,需要的朋友可以參考下
    2017-04-04
  • Hadoop2.8.1完全分布式環(huán)境搭建過(guò)程

    Hadoop2.8.1完全分布式環(huán)境搭建過(guò)程

    本文搭建了一個(gè)由三節(jié)點(diǎn)(master、slave1、slave2)構(gòu)成的Hadoop完全分布式集群(區(qū)別單節(jié)點(diǎn)偽分布式集群),并通過(guò)Hadoop分布式計(jì)算的一個(gè)示例測(cè)試集群的正確性。對(duì)hadoop分布式環(huán)境搭建過(guò)程感興趣的朋友跟隨小編一起看看吧
    2019-06-06
  • 淺談Java枚舉的作用與好處

    淺談Java枚舉的作用與好處

    下面小編就為大家?guī)?lái)一篇淺談Java枚舉的作用與好處。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-07-07

最新評(píng)論