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