ThreadLocal導(dǎo)致JVM內(nèi)存泄漏原因探究
為什么要使用ThreadLocal
在一整個業(yè)務(wù)邏輯流程中,為了在不同的地方或者不同的方法中使用同一個對象,但是又不想在方法形參中加這個對象,那么就可以使用ThreadLocal來保存
ThreadLocal最大的應(yīng)用場景就是跨方法進(jìn)行參數(shù)傳遞
ThreadLocal可以給每一個線程綁定一個變量的副本
使用ThreadLocal
ThreadLocal常用的方法其實(shí)也就下面幾個
// 返回當(dāng)前線程所對應(yīng)的線程局部變量。 public T get() {} // 設(shè)置當(dāng)前線程的線程局部變量的值。 public void set(T value) {} // 移除,當(dāng)線程結(jié)束后,該線程thread對象中的局部變量將在下一次gc時回收,如果顯示的調(diào)用此方法只是可以加快內(nèi)存回收的速度 // 所以javase開發(fā) 普通new Thread()方式中,這個方法并不是必須要調(diào)用的 // 但是javaWeb開發(fā)中就必須顯示調(diào)用,因?yàn)閖avaweb都是使用的線程池,并不是一個客戶端來一個請求,thread線程對象用完就刪除,而是會放回線程池中。 public void remove() {} // 返回該線程局部變量的一個初始化 // protected方法,顯然是為了讓子類覆蓋而設(shè)計(jì)的。這個方法在第一次調(diào)用 get()或 set(Object)時才執(zhí)行,并且僅執(zhí)行 1 次 protected T initialValue() {}
在具體使用的時候,我們ThreadLocal對象一定會定義成靜態(tài)的,如果不定義成靜態(tài)的那么其他地方如何通過這個ThreadLocal實(shí)例去Map中拿數(shù)據(jù)嘞?
而且如果是多個線程保存一個變量的副本,一個靜態(tài)的ThreadLocal也足夠了,因?yàn)樗亲鳛槎鄠€map中的key存在的
簡單使用案例
/** * @Description: 在一個方法中調(diào)用set()方法存值,在另一個方法中調(diào)用get()方法取值 */ public class UseThreadLocalTest { public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); /** * 創(chuàng)建一個線程類 */ 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()); } } /** * 開三個線程 */ 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()方法的源碼如下
// 存值時 map最終是存儲在當(dāng)前線程Thread t = Thread.currentThread()中的,是thread的一個成員變量 // map的key是當(dāng)前threadLocal對象實(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); } // 取值時也是也是先從當(dāng)前線程Thread對象中取出map // 然后在從map中根據(jù)當(dāng)前threadLocal對象實(shí)例作為key獲取到entry對象 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(); }
為了提高性能,才沒有采用加鎖的方式,而是將map和各個線程thread對象進(jìn)行關(guān)聯(lián),這樣就避免了產(chǎn)生線程安全問題,也避免了加鎖,提高了性能
我們接下來再來看看ThreadLocalMap
它的實(shí)現(xiàn),它類似于jdk1.7版本的hashmap,底層存儲的是一個Entry對象的數(shù)組,初始容量也是16,存值時先用hash結(jié)果和數(shù)組長度取余得到數(shù)組下標(biāo)位置,然后判斷是否產(chǎn)生了hash沖突,然后使用開發(fā)定址法來處理。根據(jù)算法的不同又可以分為線性探測再散列、二次探測再散列、偽隨機(jī)探測再散列。ThreadLocalMap
它是使用的線性探測再散列法,如下所示
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
Entry對象中的key
它是一個弱引用,Entry繼承了WeakReference
類,弱引用跟沒引用差不多,GC會直接回收掉,不管內(nèi)存是否足夠都會回收
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
引發(fā)內(nèi)存泄漏的原因
上面再介紹ThreadLocal基本使用api方法的時候也提到了,如果只是創(chuàng)建一個普通的線程Thread對象,是不會產(chǎn)生內(nèi)存泄漏問題的。因?yàn)閙ap是存儲在Thread對象中,一個普通線程執(zhí)行完了,那么這個線程的局部變量也就會被gc回收。
但如果結(jié)合到了線程池,一個Thread線程對象用完后放回線程池中,如果這個時候我們程序不顯示的調(diào)用remove()
方法,那么就會造成內(nèi)存泄漏問題了。
因?yàn)镋ntry對象中的Key的弱引用,但是value還會存在,就會存在map中key為null的value
ThreadLocal 的底層實(shí)現(xiàn)中我們可以看見,無論是 get()
、set()
在某些時 候,調(diào)用了 expungeStaleEntry()
方法用來清除 Entry 中 Key 為 null 的 Value,但是這是不及時的,也不是每次都會執(zhí)行的,所以一些情況下還是會發(fā)生內(nèi)存泄露。
到此這篇關(guān)于ThreadLocal導(dǎo)致JVM內(nèi)存泄漏原因探究的文章就介紹到這了,更多相關(guān)JVM內(nèi)存泄漏內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合Mongodb實(shí)現(xiàn)增刪查改的方法
這篇文章主要介紹了SpringBoot整合Mongodb實(shí)現(xiàn)簡單的增刪查改,MongoDB是一個以分布式數(shù)據(jù)庫為核心的數(shù)據(jù)庫,因此高可用性、橫向擴(kuò)展和地理分布是內(nèi)置的,并且易于使用。況且,MongoDB是免費(fèi)的,開源的,感興趣的朋友跟隨小編一起看看吧2022-05-05SpringBoot消息國際化配置實(shí)現(xiàn)過程解析
這篇文章主要介紹了SpringBoot消息國際化配置實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07SpringBoot 整合Redisson重寫cacheName支持多參數(shù)的案例代碼
這篇文章主要介紹了SpringBoot 整合Redisson重寫cacheName支持多參數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01SpringMVC4+MyBatis+SQL Server2014實(shí)現(xiàn)數(shù)據(jù)庫讀寫分離
這篇文章主要介紹了SpringMVC4+MyBatis+SQL Server2014實(shí)現(xiàn)讀寫分離,需要的朋友可以參考下2017-04-04