Java中ThreadLocal避免內(nèi)存泄漏的方法詳解
ThreadLocal簡(jiǎn)介
ThreadLocal
是 Java 中的一個(gè)線程本地存儲(chǔ)機(jī)制,它允許每個(gè)線程擁有一個(gè)獨(dú)立的本地存儲(chǔ)空間,用于存儲(chǔ)該線程的變量。ThreadLocal
提供了一種簡(jiǎn)單的方式來(lái)解決多線程環(huán)境下共享變量的問(wèn)題,避免了在多線程環(huán)境下出現(xiàn)的線程安全問(wèn)題。
ThreadLocal簡(jiǎn)單用法
public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); //當(dāng)前值+1 public static void increment() { int value = threadLocal.get(); threadLocal.set(value + 1); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { String threadName = Thread.currentThread().getName(); increment(); System.out.println(threadName + " 當(dāng)前threadLocal的值為:" + threadLocal.get()); }).start(); } } }
輸出結(jié)果為
Thread-0 當(dāng)前threadLocal的值為:1
Thread-5 當(dāng)前threadLocal的值為:1
Thread-3 當(dāng)前threadLocal的值為:1
Thread-4 當(dāng)前threadLocal的值為:1
Thread-6 當(dāng)前threadLocal的值為:1
Thread-2 當(dāng)前threadLocal的值為:1
Thread-9 當(dāng)前threadLocal的值為:1
Thread-1 當(dāng)前threadLocal的值為:1
Thread-7 當(dāng)前threadLocal的值為:1
Thread-8 當(dāng)前threadLocal的值為:1
我們發(fā)現(xiàn)每個(gè)線程雖然都共享同一個(gè)threadLocal實(shí)例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨(dú)立的值,這是因?yàn)槲覀兺ㄟ^(guò)ThreadLocal為每一個(gè)線程提供了單獨(dú)的副本。
使用場(chǎng)景
spring事務(wù)模板類
//使用示例 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //查看源碼 public static TransactionStatus currentTransactionStatus() throws NoTransactionException { TransactionInfo info = currentTransactionInfo(); if (info == null || info.transactionStatus == null) { throw new NoTransactionException("No transaction aspect-managed TransactionStatus in scope"); } return info.transactionStatus; } //currentTransactionInfo方法 @Nullable protected static TransactionInfo currentTransactionInfo() throws NoTransactionException { return transactionInfoHolder.get(); } //這里使用了ThreadLocal private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction");
HttpServletRequest
項(xiàng)目中要獲取當(dāng)前HttpServletRequest可以使用
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
我們來(lái)看看RequestContextHolder.getRequestAttributes()方法
@Nullable public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context");
aop調(diào)用鏈傳遞
LCN/seata都是使用ThreadLocal傳遞調(diào)用鏈的,這里就不展開講了。
內(nèi)存泄漏與內(nèi)存溢出
內(nèi)存泄漏
內(nèi)存泄漏指的是程序中存在某些對(duì)象或資源沒有被妥善地釋放,導(dǎo)致這些對(duì)象或資源一直占用著內(nèi)存,而無(wú)法被回收。隨著時(shí)間的推移,這些未釋放的對(duì)象或資源會(huì)越來(lái)越多,最終耗盡系統(tǒng)的內(nèi)存資源,導(dǎo)致系統(tǒng)崩潰。
常見的內(nèi)存泄漏包括:
- 對(duì)象被創(chuàng)建后,沒有及時(shí)被銷毀,成為垃圾對(duì)象。
- 沒有正確關(guān)閉IO資源。
- 緩存沒有被清空。
- 靜態(tài)集合類對(duì)象未刪除引用。
- 單例模式下對(duì)象未及時(shí)釋放等。
內(nèi)存溢出
內(nèi)存溢出指的是程序在申請(qǐng)內(nèi)存時(shí),無(wú)法獲得足夠的內(nèi)存空間,導(dǎo)致程序無(wú)法正常運(yùn)行。通常情況下,當(dāng)程序需要使用的內(nèi)存超過(guò)了系統(tǒng)能夠提供的內(nèi)存時(shí),就會(huì)發(fā)生內(nèi)存溢出。
常見的內(nèi)存溢出包括:
- 堆內(nèi)存溢出:由于創(chuàng)建了過(guò)多的對(duì)象或者某些對(duì)象太大,導(dǎo)致堆內(nèi)存不足。
- 棧內(nèi)存溢出:由于方法調(diào)用過(guò)多或者某些方法的遞歸調(diào)用層數(shù)過(guò)多,導(dǎo)致棧內(nèi)存不足。
- 永久代內(nèi)存溢出:由于創(chuàng)建了過(guò)多的類或者字符串,導(dǎo)致永久代內(nèi)存不足。
區(qū)別
內(nèi)存泄漏和內(nèi)存溢出的區(qū)別在于它們發(fā)生的原因和表現(xiàn)形式。內(nèi)存泄漏是指對(duì)象或者資源無(wú)法被妥善釋放,導(dǎo)致系統(tǒng)資源浪費(fèi),而內(nèi)存溢出則是指系統(tǒng)不能分配所需內(nèi)存,導(dǎo)致程序崩潰或者異常。通常情況下,內(nèi)存泄漏會(huì)逐漸消耗系統(tǒng)資源,而內(nèi)存溢出則是突然發(fā)生的。
解決內(nèi)存泄漏的方法是找到未被正確釋放的對(duì)象或資源,并手動(dòng)進(jìn)行釋放。而解決內(nèi)存溢出的方法則需要優(yōu)化程序代碼,減少內(nèi)存使用量,或增加系統(tǒng)內(nèi)存大小等方式來(lái)解決。
java強(qiáng)軟弱虛
Java中的引用類型有四種:強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)。它們之間的主要區(qū)別在于對(duì)象被垃圾回收時(shí)的行為不同。
強(qiáng)引用
強(qiáng)引用是默認(rèn)類型的引用,當(dāng)我們通過(guò)“new”關(guān)鍵字創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象會(huì)被分配到堆內(nèi)存中,并且默認(rèn)情況下,該對(duì)象的引用是強(qiáng)引用。只要強(qiáng)引用存在,垃圾回收器就不會(huì)將其回收。
例如:
Object obj = new Object();
在上面的代碼中,obj是一個(gè)強(qiáng)引用,因此只有當(dāng)obj變量被顯示地設(shè)置為null時(shí),才能使對(duì)象成為垃圾,等待垃圾回收器收集。
軟引用
軟引用可以讓對(duì)象存活更長(zhǎng)時(shí)間,直到內(nèi)存不足時(shí)才回收它。如果垃圾回收器需要更多的內(nèi)存,則會(huì)回收只被軟引用引用的對(duì)象。當(dāng)一個(gè)對(duì)象只被軟引用引用時(shí),它會(huì)被保留在內(nèi)存中,直到系統(tǒng)內(nèi)存不夠用或者垃圾回收器需要更多空間為止。通過(guò)軟引用可以實(shí)現(xiàn)一些緩存功能。
例如:
SoftReference<Object> softRef = new SoftReference<>(new Object());
在上面的代碼中,softRef是一個(gè)軟引用,當(dāng)垃圾回收器需要內(nèi)存時(shí),它可以將該對(duì)象回收,并釋放所占用的內(nèi)存。
弱引用
弱引用比軟引用生命期更短,當(dāng)一個(gè)對(duì)象只被弱引用引用時(shí),當(dāng)垃圾回收器運(yùn)行時(shí),不管當(dāng)前內(nèi)存是否充足,都會(huì)將其回收。弱引用通常用于實(shí)現(xiàn)緩存機(jī)制或者觀察者模式。
例如:
WeakReference<Object> weakRef = new WeakReference<>(new Object());
在上面的代碼中,weakRef是一個(gè)弱引用,這意味著垃圾回收器可以隨時(shí)將該對(duì)象回收,而無(wú)需考慮系統(tǒng)內(nèi)存是否充足。
虛引用
虛引用也稱為幽靈引用,與其他三種引用方式不同,它并不會(huì)決定對(duì)象是否能存活。如果一個(gè)對(duì)象只有虛引用,那么就像沒有任何引用一樣,它的內(nèi)存會(huì)被回收,但是在回收之前會(huì)調(diào)用finalize()方法。虛引用主要用于管理DirectBuffer的生命周期。
例如:
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), null);
在上面的代碼中,phantomRef是一個(gè)虛引用,當(dāng)垃圾回收器發(fā)現(xiàn)該對(duì)象的內(nèi)存已被回收時(shí),它會(huì)將其插入隊(duì)列中,并在下一次調(diào)用垃圾回收器時(shí)通知引用對(duì)象被回收了。
ThreadLocal原理
ThreadLocal
ThreadLocal是一個(gè)泛型類,它提供了get()、set()和remove()方法來(lái)獲取、設(shè)置和刪除當(dāng)前線程的變量副本。它的原理是在每個(gè)Thread對(duì)象中都有一個(gè)ThreadLocalMap類型的私有變量threadLocals,該變量存儲(chǔ)著當(dāng)前線程所對(duì)應(yīng)的所有ThreadLocal變量的值。
public T get() { //獲取當(dāng)前線程 Thread t = Thread.currentThread(); //獲取當(dāng)前線程的ThreadLocalMap變量 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(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap
ThreadLocalMap是ThreadLocal的內(nèi)部類,它實(shí)際上就是一個(gè)HashMap,用于存儲(chǔ)當(dāng)前線程所對(duì)應(yīng)的所有ThreadLocal變量的值。每個(gè)ThreadLocal對(duì)象都會(huì)被保存在ThreadLocalMap中,并且使用ThreadLocal作為key來(lái)訪問(wèn)它的變量值。這樣做的好處是每個(gè)線程都可以獨(dú)立維護(hù)自己的數(shù)據(jù),而不會(huì)與其他線程產(chǎn)生沖突。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } ThreadLocal.ThreadLocalMap threadLocals = null;
這里需要注意的是ThreadLocalMap每個(gè)元素都是Entry,而它是用弱引用對(duì)象作為key存儲(chǔ)在ThreadLocalMap中
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
實(shí)現(xiàn)原理
當(dāng)我們通過(guò)ThreadLocal類創(chuàng)建一個(gè)新的變量時(shí),實(shí)際上是在當(dāng)前線程的threadLocals變量中創(chuàng)建了一個(gè)新的Entry對(duì)象,該對(duì)象的key是ThreadLocal對(duì)象本身,value則是我們?cè)O(shè)置的變量值。這個(gè)Entry對(duì)象會(huì)存儲(chǔ)在ThreadLocalMap中。
當(dāng)我們需要在當(dāng)前線程中訪問(wèn)這個(gè)變量時(shí),ThreadLocal會(huì)根據(jù)當(dāng)前線程獲取對(duì)應(yīng)的ThreadLocalMap對(duì)象,并根據(jù)ThreadLocal對(duì)象作為key來(lái)查找該變量的值。由于每個(gè)線程都有自己獨(dú)立的ThreadLocalMap對(duì)象,因此不同線程之間的變量互不干擾。
ThreadLocal內(nèi)存泄漏原因
每個(gè)ThreadLocal對(duì)象都會(huì)被存儲(chǔ)在當(dāng)前線程的ThreadLocalMap中,并且使用ThreadLocal對(duì)象作為key來(lái)訪問(wèn)它的變量值。由于使用的是弱引用對(duì)象作為key,當(dāng)一個(gè)ThreadLocal對(duì)象沒有被任何線程引用時(shí),該對(duì)象就會(huì)被回收。
但是,即使ThreadLocal對(duì)象已經(jīng)被回收,對(duì)應(yīng)的變量副本仍然存在于該線程的ThreadLocalMap中。這是因?yàn)門hreadLocalMap內(nèi)部使用了強(qiáng)引用對(duì)象(Entry對(duì)象)來(lái)引用變量副本,只有在當(dāng)前線程被回收時(shí),ThreadLocalMap中對(duì)應(yīng)的Entry才會(huì)被回收。
也就是說(shuō),ThreadLocal對(duì)象雖然使用的是弱引用,但是與之關(guān)聯(lián)的變量副本卻是通過(guò)強(qiáng)引用對(duì)象間接引用的,因此在ThreadLocal對(duì)象被回收后,其變量副本可能不會(huì)立刻被回收。如果我們沒有手動(dòng)調(diào)用remove()方法將變量副本從ThreadLocalMap中清除,那么它就會(huì)一直存在于內(nèi)存中,從而導(dǎo)致內(nèi)存泄漏問(wèn)題。
ThreadLocal內(nèi)存泄漏常見場(chǎng)景
ThreadLocal內(nèi)存泄漏的原因主要是由于線程復(fù)用導(dǎo)致的。
線程池
當(dāng)我們使用線程池時(shí),如果在線程中使用了ThreadLocal變量,那么該變量并不會(huì)被自動(dòng)清除。線程池中的線程是可以被重復(fù)利用的,如果我們?cè)谝粋€(gè)線程中使用了ThreadLocal變量,并且沒有在該線程結(jié)束前手動(dòng)清除它,那么這個(gè)變量將會(huì)一直存在于ThreadLocalMap中,即使該線程已經(jīng)被回收,這就會(huì)導(dǎo)致內(nèi)存泄漏。
長(zhǎng)時(shí)間持有
如果我們?cè)谝粋€(gè)線程中創(chuàng)建了ThreadLocal變量,并且一直持有它卻不使用,這也會(huì)導(dǎo)致內(nèi)存泄漏問(wèn)題。在這種情況下,由于該變量一直存在于ThreadLocalMap中,即使該線程已經(jīng)被回收,該變量也無(wú)法被釋放,最終會(huì)導(dǎo)致內(nèi)存泄漏。
ThreadLocal避免內(nèi)存泄漏方法
為了避免ThreadLocal內(nèi)存泄漏問(wèn)題,我們可以采取以下措施:
- 在使用完ThreadLocal變量后,應(yīng)該盡快調(diào)用remove()方法將其從ThreadLocalMap中清除,以便讓垃圾回收器回收它們。
- 將ThreadLocal變量定義成private static類型的,并且在使用完之后手動(dòng)清除,以避免線程重用時(shí)引起的內(nèi)存泄漏問(wèn)題。
- 不要在線程池中使用ThreadLocal變量,如果必須使用,應(yīng)該在使用完后手動(dòng)清理。
總結(jié)
雖然ThreadLocal可以解決線程安全問(wèn)題,但是在使用完之后需要手動(dòng)清除,以避免線程重用時(shí)引起的內(nèi)存泄漏問(wèn)題。
以上就是Java中ThreadLocal避免內(nèi)存泄漏的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Java ThreadLocal避免內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot集成tika實(shí)現(xiàn)word轉(zhuǎn)html的操作代碼
Tika是一個(gè)內(nèi)容分析工具,自帶全面的parser工具類,能解析基本所有常見格式的文件,得到文件的metadata,content等內(nèi)容,返回格式化信息,本文給大家介紹了SpringBoot集成tika實(shí)現(xiàn)word轉(zhuǎn)html的操作,需要的朋友可以參考下2024-06-06java?ResourceBundle讀取properties文件方式
這篇文章主要介紹了java?ResourceBundle讀取properties文件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Java修改maven的默認(rèn)jdk版本為1.7的方法
這篇文章主要介紹了Java修改maven的默認(rèn)jdk版本為1.7的方法,需要的朋友可以參考下2018-02-02java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList
這篇文章主要介紹了java中為什么要謹(jǐn)慎使用Arrays.asList、ArrayList的subList,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02使用位運(yùn)算、值交換等方式反轉(zhuǎn)java字符串的多種方法(四種方法)
這篇文章主要介紹了使用位運(yùn)算、值交換等方式反轉(zhuǎn)java字符串,本文通過(guò)四種方式給大家講解,給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Java基于ShardingSphere實(shí)現(xiàn)分庫(kù)分表的實(shí)例詳解
ShardingSphere?已于2020年4月16日成為?Apache?軟件基金會(huì)的頂級(jí)項(xiàng)目,?它們均提供標(biāo)準(zhǔn)化的數(shù)據(jù)水平擴(kuò)展、分布式事務(wù)和分布式治理等功能,可適用于如?Java?同構(gòu)、異構(gòu)語(yǔ)言、云原生等各種多樣化的應(yīng)用場(chǎng)景,對(duì)ShardingSphere分庫(kù)分表相關(guān)知識(shí)感興趣的朋友一起看看吧2022-03-03java使用異或?qū)崿F(xiàn)變量互換和異或加密解密示例
這篇文章主要介紹了使用異或?qū)崿F(xiàn)變量互換和異或加密解密示例,需要的朋友可以參考下2014-02-02java編程無(wú)向圖結(jié)構(gòu)的存儲(chǔ)及DFS操作代碼詳解
這篇文章主要介紹了java編程無(wú)向圖結(jié)構(gòu)的存儲(chǔ)及DFS操作代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以了解下。2017-12-12