ThreadLocal內(nèi)存泄漏問(wèn)題解決方案
如果說(shuō) ThreadLocal 的話,那肯定就會(huì)涉及到內(nèi)存泄漏,為啥嘞
因?yàn)?吧啦吧啦 ~
ThreadLocal 解決了什么問(wèn)題呢?
它是為了解決對(duì)象不能被多線程共享訪問(wèn)的問(wèn)題,通過(guò) threadLocal.set() 方法將對(duì)象實(shí)例保存在每個(gè)線程自己所擁有的 threadLocalMap 中,這樣的話每個(gè)線程都使用自己的對(duì)象實(shí)例,彼此不會(huì)影響從而達(dá)到了隔離的作用,這樣就解決了對(duì)象在被共享訪問(wèn)時(shí)帶來(lái)的線程安全問(wèn)題。
啥意思呢?打個(gè)比方,現(xiàn)在公司所有人都要填寫一個(gè)表格,但是只有一支筆,這個(gè)時(shí)候就只能上個(gè)人用完了之后,下個(gè)人才可以使用,為了保證"筆"這個(gè)資源的可用性,只需要保證在接下來(lái)每個(gè)人的獲取順序就可以了,這就是 lock 的作用,當(dāng)這支筆被別人用的時(shí)候,我就加 lock ,你來(lái)了那就進(jìn)入隊(duì)列排隊(duì)等待獲取資源(非公平方式那就另外說(shuō)了),這支筆用完之后就釋放 lock ,然后按照順序給下個(gè)人使用。
但是完全可以一個(gè)人一支筆對(duì)不對(duì),這樣的話,你填寫你的表格,我填寫我的表格,咱倆誰(shuí)都不耽擱誰(shuí)。這就是 ThreadLocal 在做的事情,因?yàn)槊總€(gè) Thread 都有一個(gè)副本,就不存在資源競(jìng)爭(zhēng),所以也就不需要加鎖,這不就是拿空間去換了時(shí)間嘛!
在開(kāi)始之前,咱們先把 Thread, ThreadLocal, ThreadLocalMap 的關(guān)系捋一捋:

可以看到,在 Thread 中持有一個(gè) ThreadLocalMap , ThreadLocalMap 又是由 Entry 來(lái)組成的,在 Entry 里面有 ThreadLocal 和 value
ThreadLocal 為啥動(dòng)不動(dòng)就內(nèi)存泄漏呢?
在這里先給個(gè)解釋,后面咱們?cè)僭敿?xì)分析:
首先是因?yàn)?ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的,其中 ThreadLocalMap 的 Entry 繼承了 WeakReference ,而 Entry 對(duì)象中的 key 使用了 WeakReference 封裝,也就是說(shuō), Entry 中的 key 是一個(gè)弱引用類型,對(duì)于弱引用來(lái)說(shuō),它只能存活到下次 GC 之前
如果此時(shí)一個(gè)線程調(diào)用了 ThreadLocalMap 的 set 設(shè)置變量,當(dāng)前的 ThreadLocalMap 就會(huì)新增一條記錄,但由于發(fā)生了一次垃圾回收,這樣就會(huì)造成一個(gè)結(jié)果: key 值被回收掉了,但是 value 值還在內(nèi)存中,而且如果線程一直存在的話,那么它的 value 值就會(huì)一直存在
這樣被垃圾回收掉的 key 就會(huì)一直存在一條引用鏈: Thread -> ThreadLocalMap -> Entry -> Value :

就是因?yàn)檫@條引用鏈的存在,就會(huì)導(dǎo)致如果 Thread 還在運(yùn)行,那么 Entry 不會(huì)被回收,進(jìn)而 value 也不會(huì)被回收掉,但是 Entry 里面的 key 值已經(jīng)被回收掉了
這只是一個(gè)線程,如果再來(lái)一個(gè)線程,又來(lái)一個(gè)線程…多了之后就會(huì)造成內(nèi)存泄漏
知道是怎么造成內(nèi)存泄漏之后,接下來(lái)要做的事情就好說(shuō)了,不是因?yàn)?value 值沒(méi)有被回收掉所以才會(huì)導(dǎo)致內(nèi)存泄露的嘛
那使用完 key 值之后,將 value 值通過(guò) remove 方法 remove 掉,這樣的話內(nèi)存中就不會(huì)有 value 值了,也就防止了內(nèi)存泄漏嘛
ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的?
OK ,上面的內(nèi)容講完了,接下來(lái)一一來(lái)看
首先,你怎么知道 ThreadLocal 是基于 ThreadLocalMap 實(shí)現(xiàn)的呢?
從源碼知道的~
在源碼中能夠看到下面這幾行代碼:
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
} } } }
代碼中說(shuō)的很清楚了,在 ThreadLocal 內(nèi)部維護(hù)著 ThreadLocalMap ,而它的 Entry 則繼承自 WeakReference 的 ThreadLocal ,其中 Entry 的 k 為 ThreadLocal , v 為 Object ,在調(diào)用 super(k) 時(shí)就將 ThreadLocal 實(shí)例包裝成了一個(gè) WeakReference
強(qiáng)弱引用這塊內(nèi)容阿粉就直接放一個(gè)表格吧:
| 引用類型 | 功能特點(diǎn) |
|---|---|
| 強(qiáng)引用 ( Strong Reference ) | 被強(qiáng)引用關(guān)聯(lián)的對(duì)象永遠(yuǎn)不會(huì)被垃圾回收器回收掉 |
| 軟引用( Soft Reference ) | 軟引用關(guān)聯(lián)的對(duì)象,只有當(dāng)系統(tǒng)將要發(fā)生內(nèi)存溢出時(shí),才會(huì)去回收軟引用引用的對(duì)象 |
| 弱引用 ( Weak Reference ) | 只被弱引用關(guān)聯(lián)的對(duì)象,只要發(fā)生垃圾收集事件,就會(huì)被回收 |
| 虛引用 ( Phantom Reference ) | 被虛引用關(guān)聯(lián)的對(duì)象的唯一作用是能在這個(gè)對(duì)象被回收器回收時(shí)收到一個(gè)系統(tǒng)通知 |
從表格中應(yīng)該能夠看出來(lái),弱引用的對(duì)象只要發(fā)生垃圾收集事件,就會(huì)被回收
所以弱引用的存活時(shí)間也就是下次 GC 之前了
在這里阿粉就有個(gè)問(wèn)題想問(wèn)問(wèn)了:為什么 ThreadLocal 采用弱引用,而不是強(qiáng)引用嘞?
在 ThreadLocalMap 上面有些注釋,我在這里摘錄一部分,或許可以從中窺探一二:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys
翻譯一下就是:(雖然我英語(yǔ)不是很好
為了解決非常大且長(zhǎng)期使用的問(wèn)題,哈希表使用了弱引用的 key
假設(shè),假設(shè), ThreadLocal 使用的是強(qiáng)引用,會(huì)怎樣呢?
如果是強(qiáng)引用的話,在表格中也能夠看出來(lái),被強(qiáng)引用關(guān)聯(lián)的對(duì)象,永遠(yuǎn)都不會(huì)被垃圾回收器回收掉
如果引用的 ThreadLocal 對(duì)象被回收了,但是 ThreadLocalMap 還持有對(duì) ThreadLocal 的強(qiáng)引用,如果沒(méi)有 remove 的話, 在 GC 時(shí)進(jìn)行可達(dá)性分析, ThreadLocal 依然可達(dá),這樣就不會(huì)對(duì) ThreadLocal 進(jìn)行回收,但是我們期望的是引用的 ThreadLocal 對(duì)象被回收,這樣不就達(dá)不到目的了嘛
使用弱引用的話,雖然會(huì)出現(xiàn)內(nèi)存泄漏的問(wèn)題,但是在 ThreadLocal 生命周期里面,都有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作
所以,使用弱引用的話,可以在 ThreadLocal 生命周期中盡可能保證不出現(xiàn)內(nèi)存泄漏的問(wèn)題
啥?在 ThreadLcoal 生命周期里面,都有對(duì) key 值為 null 時(shí)進(jìn)行回收的處理操作?有證據(jù)么?
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springMVC中HttpMessageConverter的具體使用
HttpMessageConverter,報(bào)文信息轉(zhuǎn)換器,將請(qǐng)求報(bào)文轉(zhuǎn)換為Java對(duì)象,本文主要介紹了springMVC中HttpMessageConverter的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08
spring boot 即時(shí)重新啟動(dòng)(熱更替)使用說(shuō)明
這篇文章主要介紹了spring boot 即時(shí)重新啟動(dòng)(熱更替)的相關(guān)資料,需要的朋友可以參考下2017-12-12
Mybatis返回?cái)?shù)組的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了Mybatis返回?cái)?shù)組的兩種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03
解決SpringBoot項(xiàng)目使用多線程處理任務(wù)時(shí)無(wú)法通過(guò)@Autowired注入bean問(wèn)題
這篇文章主要介紹了SpringBoot項(xiàng)目使用多線程處理任務(wù)時(shí)無(wú)法通過(guò)@Autowired注入bean問(wèn)題的解決方法,需要的朋友可以參考下2018-09-09
SpringBoot中的定時(shí)任務(wù)和異步調(diào)用詳解
這篇文章主要介紹了SpringBoot中的定時(shí)任務(wù)和異步調(diào)用詳解,SpringBoot 定時(shí)任務(wù)是一種在SpringBoot應(yīng)用中自動(dòng)執(zhí)行任務(wù)的機(jī)制,通過(guò)使用Spring框架提供的@Scheduled注解,我們可以輕松地創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下2023-10-10
java后臺(tái)實(shí)現(xiàn)支付寶對(duì)賬功能的示例代碼
這篇文章主要介紹了java后臺(tái)實(shí)現(xiàn)支付寶對(duì)賬功能的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
Spring Boot 3 集成 RabbitMQ 實(shí)踐指南(原理解析
本文介紹了SpringBoot 3集成RabbitMQ的實(shí)踐指南,涵蓋了RabbitMQ的核心原理、核心概念、高級(jí)特性、應(yīng)用場(chǎng)景、環(huán)境搭建、核心配置類、消息生產(chǎn)者、消息消費(fèi)者、接口控制器、監(jiān)控與運(yùn)維、最佳實(shí)踐以及常見(jiàn)問(wèn)題與解決方案等內(nèi)容,感興趣的朋友一起看看吧2025-02-02
java利用Tabula實(shí)現(xiàn)對(duì)PDF內(nèi)表格數(shù)據(jù)提取
Tabula是一個(gè)開(kāi)源工具,用于從PDF文檔中提取表格數(shù)據(jù),下面小編就來(lái)和大家詳細(xì)介紹一下java如何通過(guò)Tabula對(duì)PDF文件內(nèi)表格進(jìn)行數(shù)據(jù)提取吧2023-09-09

