作者:Grey
原文位址:Java中的引用類型和使用場景
Java中的引用類型分成強引用, 軟引用, 弱引用, 虛引用。
沒有引用指向這個對象,垃圾回收會回收
當有一個對象被一個軟引用所指向的時候,隻有系統記憶體不夠用的時候,才會被回收,可以用做緩存(比如緩存大圖檔)
示例如下代碼:注:執行以下方法的時候,需要把VM options設定為<code>-Xms20M -Xmx20M</code>。
上述代碼在第一次執行<code>System.out.println(reference.get())</code>時候,由于堆的最大最小值都是<code>20M</code>,而我們配置設定的<code>byte</code>數組是<code>10M</code>,沒有超過最大堆記憶體,是以執行垃圾回收,軟引用不被回收,後續又調用了<code>byte[] bytes = new byte[1024 * 1024 * 10];</code>再次配置設定了<code>10M</code>記憶體,此時堆記憶體已經超過設定的最大值,會進行回收,是以最後一步的<code>System.out.println(reference.get());</code>無法<code>get</code>到資料。
隻要垃圾回收,就會回收。如果有一個強引用指向弱引用中的這個對象,如果這個強引用消失,這個對象就應該被回收。一般用在容器裡面。
代碼示例如下:
如果執行了一次<code>GC</code>,<code>reference.get()</code> 擷取到的值即為空。
弱引用的一個典型應用場景就是<code>ThreadLocal</code>,以下是<code>ThreadLocal</code>的的簡要介紹
set方法
get方法
<code>ThreadLocalMap</code>是目前線程的一個成員變量,是以,其他線程無法讀取目前線程設定的<code>ThreadLocal</code>值。
<code>ThreadLocal</code>的主要應用場景
場景一:每個線程需要一個獨享的對象:假設有100個線程都需要用到<code>SimpleDateFormat</code>類來處理日期格式,如果共用一個<code>SimpleDateFormat</code>,就會出現線程安全問題,導緻資料出錯,如果加鎖,就會降低性能,此時使用<code>ThreadLocal</code>,給每個線程儲存一份自己的本地<code>SimpleDateFormat</code>,就可以同時保證線程安全和性能需求。
場景二:每個線程内部儲存全局變量,避免傳參麻煩:假設一個線程的作用是拿到前端使用者資訊,逐層執行<code>Service1</code>,<code>Service2</code>,<code>Service3</code>,<code>Service4</code>層的業務邏輯,其中每個業務層都會用到使用者資訊,此時一個解決辦法就是将<code>User</code>資訊對象作為參數層層傳遞,但是這樣會導緻代碼備援且不利于維護。此時可以将<code>User</code>資訊對象放入目前線程的<code>Threadlocal</code>中,就變成了全局變量,在每一層業務層中,需要使用的時候直接從<code>Threadlocal</code>中擷取即可。
場景三:<code>Spring</code>的聲明式事務,資料庫連接配接寫在配置檔案,多個方法可以支援一個完整的事務,保證多個方法是用的同一個資料庫連接配接(其實就是放在<code>ThreadLocal</code>裡面)
了解了<code>ThreadLocal</code>簡要介紹以後,我們可以深入了解一下<code>ThreadLocal</code>的一個内部原理,前面提到,<code>ThreadLocal</code>的<code>set</code>方法實際上是往目前線程的一個<code>threadLocals</code>表中插入一條記錄,而這個表中的記錄都存在一個<code>Entry</code>對象中,這個對象有一個key和一個value,<code>key</code>就是目前線程的<code>ThreadLocal</code>對象。
這個<code>Entry</code>對象繼承了<code>WeakReference</code>, 且構造函數調用了<code>super(k)</code>, 是以<code>Entry</code>中的<code>key</code>是通過一個弱引用指向的<code>ThreadLocal</code>,是以,我們在主方法中調用
<code>tl</code>是通過強引用指向這個<code>ThreadLocal</code>對象。
目前線程的<code>threadLocalMap</code>中的<code>key</code>是通過弱引用指向<code>ThreadLocal</code>對象,這樣就可以保證,在<code>tl</code>指向空以後,這個<code>ThreadLocal</code>會被回收,否則,如果<code>threadLocalMap</code>中的<code>key</code>是強引用指向<code>ThreadLocal</code>對象話,這個<code>ThreadLocal</code>對象永遠不會被回收。就會導緻記憶體洩漏。
但是,即便<code>key</code>用弱引用指向<code>ThreadLocal</code>對象,<code>key</code>值被回收後,<code>Entry</code>中的<code>value</code>值就無法被通路到了,且<code>value</code>是通過強引用關聯,是以,也會導緻記憶體洩漏,是以,每次在<code>ThreadLocal</code>中的對象不用了,記得要調用<code>remove</code>方法,把對應的<code>value</code>也給清掉。
用于管理堆外記憶體回收
虛引用關聯了一個對象,以及一個隊列,隻要垃圾回收,虛引用就被回收,一旦虛引用被回收,虛引用會被裝到這個隊列,并會收到一個通知(如果有值入隊列,會得到一個通知)是以,如果想知道虛引用何時被回收,就隻需要不斷監控這個隊列是否有元素加入進來了。
虛引用裡面關聯的對象用get方法是無法擷取的。
JDK的<code>NIO</code>包中有一個<code>DirectByteBuffer</code>, 這個<code>buffer</code>指向的是堆外記憶體,是以當這個<code>buffer</code>設定為空的時候,Java的垃圾回收無法回收,是以,可以用虛引用來管理這個<code>buffer</code>,當我們檢測到這個虛引用被垃圾回收器回收的時候,可以做出相應的處理,去回收堆外記憶體。
juc