這里主要談一下threadlocal的內存泄漏問題,看過源碼都知道,ThreadLocal的實現(xiàn)原理就是在Thread中維護了一個私有屬性ThreadLocalMap,key為threadlocal對象的弱引用,value為自定義的值。但是這里為什么要使用弱引用,內存泄漏真的跟threadlocal的弱引用有關系么?還有弱引用的變量不是說只能存活到下次gc之前么,會不會出現(xiàn)在使用時threadlocal已經被回收掉的情況?這里我首先介紹一下弱引用,以及threadlocal發(fā)生內存泄漏的場景,緊接著這兩個問題簡單談一下我理解到的東西。
首先介紹下弱引用的定義:
在jdk1.2之后java對引用概念才進行了擴充,將對象分為了強引用,軟引用,弱引用,虛引用4種類型,引用強度是依次逐漸減弱的。
強引用,就是普遍存在代碼中的Object o = new object();這種形式,除非置為null,jvm判斷GCroot不可達,才會回收該對象。
軟引用,主要用來描述一些有用但是非必須的對象,這類對象在將要發(fā)生內存溢出時,會把這些對象進行回收
弱引用,只能活到下次gc之前
虛引用,無任何實際意義,是能用來作為該對象被回收時可以發(fā)起一個系統(tǒng)通知。
綜合這段代碼談一下threadlocal發(fā)生內存泄漏的場景:
ThreadLocal<Object> current = new ThreadLocal<>();
current.set(“str”)
current = null;
我們在ThreadLocal<Object> current = new ThreadLocal<>();之后會有個強引用指向該對象,
在current.set(“str")之后會創(chuàng)建一個entry置于threadlocalMap中的table數組中,這個entry的key是threadlocal的弱引用。
現(xiàn)在該threadlocal存在著一個current的強引用關系,還有一個threadlocalMap對threadlocal的弱引用關系,如果說我們不把current賦值為null,那該threadlocal對象永遠不會被回收,因為該塊堆內存空間仍然存在強引用。這里的內存泄漏實際上是指current置為null,就算經過了多次gc,當前線程thread還沒有被回收的情況下,當前線程中的threadlocalMap中仍然會存在對該entry的引用,因為entry中的value不為null,仍然不能被回收。如果使用的是線程池,線程回收之后還沒被銷毀,那么你需要手動去清理下線程中的threadlocalMap,即調用threadlocal的remove方法,不然很容易發(fā)生內存泄漏的問題。
問題1:這里為什么要使用弱引用,內存泄漏真的跟threadlocal的弱引用有關系么,為啥不用強引用?
為什么明知道可能會有發(fā)生內存泄漏的風險,為啥還要使用弱引用呢。如果我們這里使用的是強引用,這些上述場景中的問題都會出現(xiàn),而且計算我們手動把外層的current置為null,但是thread中的threadlocalmap中的entry的強引用還是存在,如果不去手動把key設置為null,那我們就只能通過手動刪除threadlocalmap中當前entry的形式去實現(xiàn)entry的回收,必須時刻關注回收entry的問題,發(fā)生內存泄漏的風險較高,與java內存管理自動化的理念不合。使用弱引用我們在使用threadlocal的get和set,remove方法時可以自動去把jvm已經gc掉的threadlocal對應的entry給清理掉,比強引用好很多。
問題2:會不會出現(xiàn)在使用時threadlocal已經被回收掉的情況?
threadlocal被回收肯定是當前threadlocal對象的強引用current已經置為null,這個時候說明我們程序中這個threadlocal已經沒有使用價值,在下次gc之前才會被回收。實際上我們平時使用的threadlocal基本都是靜態(tài)變量,對線程內的所有實例都是共享的,只會在類卸載的時候才會回收,基本不會因為弱引用而被gc掉。只需要注意使用線程池時,線程回收時要記得清理thread中的threadlocalMap,不然未被銷毀的線程再次被使用時仍然拿得到上次設置的線程獨享的變量
實際的使用場景:
我們項目中使用的大多數場景都是把threadlocal作為靜態(tài)變量去使用的,使用量不大,比如我們項目多用于租戶的切換,每次在攔截器中對該threadlocal對象賦值租戶的標識以供切換數據庫,還有就是用Threadlocal去實現(xiàn)simpleDateFormat的線程安全,不需要太多關注內存泄漏的場景。需要注意的主要是在使用線程池時,要注意線程池中的部分線程是可以循環(huán)利用,不會銷毀初始化的,線程回收的時候如果不去清除當前thread里的threadlocalmap可能會存在大量的內存泄漏情況,還可能會造成臟數據的讀取。