1. 程式人生 > >基礎篇:JAVA引用型別和ThreadLocal

基礎篇:JAVA引用型別和ThreadLocal

# 前言 平時併發程式設計,除了維護修改共享變數的場景,有時我們也需要為每一個執行緒設定一個私有的變數,進行執行緒隔離,java提供的ThreadLocal可以幫助我們實現,而講到ThreadLocal則不得不講講java的四種引用,不同的引用型別在GC時表現是不一樣的,引用型別Reference有助於我們瞭解如何快速回收某些物件的記憶體或對例項的GC控制 - 四種引用型別在JVM的生命週期 - 引用佇列(ReferenceQueue) - ThreadLocal的實現原理和使用 - FinalReference和finalize方法的實現原理 - Cheaner機制 關注公眾號,一起交流,微信搜一搜: 潛行前行 --- ## 1 四種引用型別在JVM的生命週期 ### 強引用(StrongReference) - 建立一個物件並賦給一個引用變數,強引用有引用變數指向時,永遠也不會垃圾回收,JVM寧願丟擲OutOfMemory異常也不會回收該物件;強引用物件的建立,如 ```java Integer index = new Integer(1); String name = "csc"; ``` - 如果中斷所有引用變數和強引用物件的聯絡(將引用變數賦值為null),JVM則會在合適的時間就會回收該物件 ### 軟引用(SoftReference) - 和強用引用不同點在於記憶體不足時,該型別引用物件會被垃圾處理器回收 - 使用軟引用能防止記憶體洩露,增強程式的健壯性。SoftReference的特點是它的一個例項儲存對一個Java物件的軟引用,該軟引用的存在不妨礙垃圾收集執行緒對該Java物件的回收 - SoftReference類所提供的get()方法返回Java物件的強引用。另外,一旦垃圾執行緒回收該物件之後,get()方法將返回null ```java String name = "csc"; //軟引用的建立 SoftReference softRef = new SoftReference(name); System.out.println(softRef.get()); ``` ### 弱引用(WeakReference) - 特點:無論記憶體是否充足,只要進行GC,都會被回收 ```java String name = "csc"; //弱引用的建立 WeakReference softRef = new WeakReference(name); System.out.println(softRef.get()); //輸出 csc System.gc(); System.out.println(softRef.get()); //輸出 null //弱引用Map WeakHashMap map = new WeakHashMap(); ``` ### 虛引用(PhantomReference) - 特點:如同虛設,和沒有引用沒什麼區別;虛引用和軟引用、弱引用不同,它並不決定物件的生命週期。如果一個物件與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收 - 要注意的是,虛引用必須和引用佇列關聯使用,當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會把這個虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收 ```java public static void main(String[] args) { String name = "csc"; ReferenceQueue queue = new ReferenceQueue(); PhantomReference pr = new PhantomReference(name, queue); //PhantomRefrence的get方法總是返回null,因此無法訪問對應的引用物件。 System.out.println(pr.get()); // null System.gc(); System.out.println(queue.poll()); //獲取被垃圾回收的"xb"的引用ReferenceQueue } ``` 引用型別 | 被垃圾回收時間 | 場景 | 生存時間 --- | --- |--- | --- 強引用 | 從來不會 | 物件的一般狀態 | JVM停止執行時終止 軟引用 | 當記憶體不足時 | 物件快取| 記憶體不足時終止 弱引用 | 正常垃圾回收時 | 物件快取| 垃圾回收後終止 虛引用 | 正常垃圾回收時 | 跟蹤物件的垃圾回收 |垃圾回收後終止 ## 2 引用佇列(ReferenceQueue) - 引用佇列可以配合軟引用、弱引用及虛引用使用;當引用的物件將要被JVM回收時,會將其加入到引用佇列中 ```java ReferenceQueue queue = new ReferenceQueue(); WeakReference pr = new WeakReference("wxj", queue); System.gc(); System.out.println(queue.poll().get()); // 獲取即將被回收的字串 wxj ``` ## 3 ThreadLocal的原理和使用 ### ThreadLocal 的實現原理 - 每個執行緒都內建了一個ThreadLocalMap物件 ```java public class Thread implements Runnable { /* 當前執行緒對於的ThreadLocalMap例項,ThreadLocal作為Key, * T對應的物件作為value */ ThreadLocal.ThreadLocalMap threadLocals = null; ``` - ThreadLocalMap作為ThreadLocal的內部類,實現了類似HashMap的功能,它元素Entry繼承於WeakReference,key值是ThreadLocal,value是引用變數。也就是說jvm發生GC時value物件則會被回收 ``` public class ThreadLocal { //ThreadLocal物件對應的hash值,使用一個靜態AtomicInteger實現 private final int threadLocalHashCode = nextHashCode(); //設定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); } //獲取value public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) {     //獲取當前執行緒的ThreadLocalMap,再使用物件ThreadLocal獲取對應的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } .... //類似HashMap的類 static class ThreadLocalMap { //使用開放地址法解決hash衝突 //如果hash出的index已經有值,通過演算法在後面的若干位置尋找空位 private Entry[] table; ... //Entry 是弱引用 static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } } ``` ### ThreadLocal不保證共享變數在多執行緒的安全性 - 從ThreadLocal的實現原理可知,ThreadLocal只是為每個執行緒儲存一個副本變數,副本變數的修改不影響其他執行緒的變數值,因此ThreadLocal不能實現共享變數的安全性 ### ThreadLocal 使用場景 - 執行緒安全,包裹執行緒不安全的工具類,比如java.text.SimpleDateFormat類,當然jdk1.8已經給出了對應的執行緒安全的類java.time.format.DateTimeFormatter - 執行緒隔離,比如資料庫連線管理、Session管理、mdc日誌追蹤等。 ### ThreadLocal記憶體洩露和WeakReference - ThreadLocalMap.Entry是弱引用,弱引用物件是不管有沒有被引用都會被垃圾回收 - 發生記憶體洩漏一般是線上程池的執行緒,生命週期長,threadLocals引用會一直存在,當其存放的ThreadLocal被回收(弱引用生命週期短)後,它對應的Entity成了e.get()==null的例項。執行緒不死則Entity一直不會被回收,這就發生了記憶體洩漏 - 如果執行緒跨業務操作相同的ThreadLocal,還會造成變數安全問題 - 通常在使用完ThreadLocal最好呼叫它的remove();在ThreadLocal的get、set的時候,最好檢查當前Entity的key是否為null,如果是null就把Entity釋放掉,value則會被垃圾回收 ## 4 finalize方法的實現原理FinalReference ```java final class Finalizer extends FinalReference {   private static ReferenceQueue queue = new ReferenceQueue<>(); /* Invoked by VM */ static void register(Object finalizee) { new Finalizer(finalizee); } private static class FinalizerThread extends Thread { .... public void run() { ... for (;;) { try { Finalizer f = (Finalizer)queue.remove(); //這裡會實現Object.finalize的呼叫 f.runFinalizer(jla); .... } static { ... Thread finalizer = new FinalizerThread(tg); ... //執行Object.finalize的守護執行緒 finalizer.setDaemon(true); finalizer.start(); } ``` - cpu資源比較稀缺的情況下FinalizerThread執行緒有可能因為優先順序比較低而延遲執行finalizer物件的finalize方法 - 因為finalizer物件的finalize方法遲遲沒有執行,有可能會導致大部分finalizer物件進入到old分代,此時容易引發old分代的gc,甚至fullgc,gc暫停時間明顯變長 ## 5 Cheaner機制 - 上一篇文章有介紹到jdk1.8的Cleaner[框架篇:ByteBuffer和netty.ByteBuf詳解](https://juejin.cn/post/6939403878366445605) 歡迎指正文中錯誤 --- # 參考文章 - [ThreadLocal原理及使用場景大揭祕](https://www.jianshu.com/p/5af663b35779) - [JDK原始碼分析之FinalReference完全解讀](https://blog.csdn.net/zero__007/article/details/60146268) - [一次 Young GC 的優化實踐](https://www.jianshu.com/p/79d4a0516f11) - [Netty資源洩露檢測](https://blog.csdn.net/yangguosb/article/details/80138719) - [避免使用Finalizer和Cleaner機制](https://www.cnblogs.com/IcanFixIt/p/8133798.html) - [【JAVA Reference】Cleaner 原始碼剖析(三)](https://blog.csdn.net/Sword52888/article/details/101