Java中的四種引用以及ReferenceQueue和WeakHashMap的使用示例
簡介:
本文主要介紹JAVA中的四種引用: StrongReference(強引用)、SoftReferenc(軟引用)、WeakReferenc(弱引用)、PhantomReference(虛引用)的作用。同時我們還將介紹ReferenceQueue和WeakHashMap的功能和使用示例。
歡迎探討,如有錯誤敬請指正
1. JAVA中的四種引用
四種引用中,軟引用、若引用、虛引用都需要相關類來建立。建立的時候都需要傳遞一個物件,然後通過引用的get方法獲取真正的物件。
1.1 StrongReference(強引用)
強引用就是我們一般在程式中引用一個物件的方式
Object obj = new Object();
obj就是一個強引用。垃圾回收器絕不會回收它,當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠回收具有強引用的物件來解決記憶體不足的問題。
1.2 SoftReference(軟引用)
軟引用的建立要藉助於java.lang.ref包下的SoftReferenc類。當JVM進行垃圾回收時,只有在記憶體不足的時候JVM才會回收僅有軟引用指向的物件所佔的空間。
package javalearning; import java.lang.ref.SoftReference; /* * 虛擬機器引數配置 * -Xms256m * -Xmx1024m */ public class SoftReferenceDemo { public static void main(String[] args){ /*軟引用物件中指向了一個長度為300000000個元素的整形陣列*/ SoftReference<int[]> softReference = new SoftReference<int[]>(new int[300000000]); /*主動呼叫一次gc,由於此時JVM的記憶體夠用,此時softReference引用的物件未被回收*/ System.gc(); System.out.println(softReference.get()); /*消耗記憶體,會導致一次自動的gc,此時JVM的記憶體不夠用 *就回收softReference物件中指向的陣列物件*/ int[] strongReference = new int[100000000]; System.out.println(softReference.get()); } }
我們應該注意到,上面的程式碼中名為softReference的引用指向了一個
SoftReference物件,這個指向還是一個強引用型別。而SoftReference物件中指向int型別陣列的引用就是一個軟引用型別了。
執行結果
[[email protected] null
1.3 WeakReference(弱引用)
弱引用的建立要藉助於java.lang.ref包下的WeakReferenc類。當JVM進行垃圾回收時,無論記憶體是否充足,都會回收僅被弱引用關聯的物件。由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快發現那些被弱引用指向的物件。
package javalearning; import java.lang.ref.WeakReference; public class WeakReferenceDemo { public static void main(String[] args){ /*若引用物件中指向了一個長度為1000個元素的整形陣列*/ WeakReference<String[]> weakReference = new WeakReference<String[]>(new String[1000]); /*未執行gc,目前僅被弱引用指向的物件還未被回收,所以結果不是null*/ System.out.println(weakReference.get()); /*執行一次gc,即使目前JVM的記憶體夠用,但還是回收僅被弱引用指向的物件*/ System.gc(); System.out.println(weakReference.get()); } }
同理,上面的程式碼中名為weakReference的引用指向了一個
WeakReference物件,這個指向還是一個強引用型別。而WeakReference物件中指向String型別陣列的引用就是一個弱引用型別了。
執行結果
[Ljava.lang.String;@2a139a55 null
1.4 PlantomReference(虛引用)
如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。建立一個虛引用物件時必須還要傳遞一個引用佇列(ReferenceQueue)。
2. ReferenceQueue(引用佇列)簡介
當gc(垃圾回收執行緒)準備回收一個物件時,如果發現它還僅有軟引用(或弱引用,或虛引用)指向它,就會在回收該物件之前,把這個軟引用(或弱引用,或虛引用)加入到與之關聯的引用佇列(ReferenceQueue)中。如果一個軟引用(或弱引用,或虛引用)物件本身在引用佇列中,就說明該引用物件所指向的物件被回收了。
當軟引用(或弱引用,或虛引用)物件所指向的物件被回收了,那麼這個引用物件本身就沒有價值了,如果程式中存在大量的這類物件(注意,我們建立的軟引用、弱引用、虛引用物件本身是個強引用,不會自動被gc回收),就會浪費記憶體。因此我們這就可以手動回收位於引用佇列中的引用物件本身。
除了上面程式碼展示的建立引用物件的方式。軟、弱、虛引用的建立還有另一種方式,即在建立引用的同時關聯一個引用佇列。
SoftReference(T referent, ReferenceQueue<? super T> q) WeakReference(T referent, ReferenceQueue<? super T> q) PhantomReference(T referent, ReferenceQueue<? super T> q)
下面的示例中我們利用ReferenceQueue回收SoftReference物件本身。
package javalearning; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; public class ReferenceQueneDemo { @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String[] args){ /*建立引用佇列*/ ReferenceQueue<SoftReference<int[]>> rq = new ReferenceQueue<SoftReference<int[]>>(); /*建立一個軟引用陣列,每一個物件都是軟引用型別*/ SoftReference<int[]>[] srArr = new SoftReference[1000]; for(int i = 0; i < srArr.length; i++){ srArr[i] = new SoftReference(new int[300000], rq); } /*(可能)在gc前保留下了三個強引用*/ int[] arr1 = srArr[30].get(); int[] arr2 = srArr[60].get(); int[] arr3 = srArr[90].get(); /*佔用記憶體,會導致一次gc,使得只有軟引用指向的物件被回收*/ int[] strongRef = new int[200000000]; Object x; int n = 0; while((x = rq.poll()) != null){ int idx = 0; while(idx < srArr.length){ if(x == srArr[idx]){ System.out.println("free " + x); srArr[idx] = null; /*手動釋放記憶體*/ n++; break; } idx++; } } /*當然最簡單的方法是通過isEnqueued()判斷一個軟引用方法是否在 * 佇列中,上面的方法只是舉例 int n = 0; for(int i = 0; i < srArr.length; i++){ if(srArr[i].isEnqueued()){ srArr[i] = null; n++; } } */ System.out.println("recycle " + n + " SoftReference Object"); } }
執行結果(省略部分結果)
…… …… …… free [email protected] free [email protected] free [email protected] free [email protected] free [email protected] recycle 997 SoftReference Object
從上面的例子中可以看出,我們回收SoftReference物件的效率並不高。原因是每從佇列中取出一個SoftReference引用,就是我們必須和SoftReference[]陣列中的每一個物件逐個比較。這樣的查詢方式顯然不及HashMap,所以我們自然想到構建一個引用型別的HashMap來解決這個問題。而實際上JDK中已經提供了一個具有這樣功能的類,即WeakHashMap。
3. WeakHashMap簡介
WeakHahsMap 的實現原理簡單來說就是HashMap裡面的條目 Entry繼承了 WeakReference,那麼當 Entry 的 key 不再被使用(即,引用物件不可達)且被 GC 後,那麼該 Entry 就會進入到 ReferenceQueue 中。當我們呼叫WeakHashMap 的get和put方法會有一個副作用,即清除無效key對應的Entry。這個過程就和上面的程式碼很類似了,首先會從引用佇列中取出一個Entry物件,然後在HashMap中查詢這個Entry物件的位置,最後把這個 Entry 從 HashMap中刪除,這時key和value物件都被回收了。重複這個過程直到佇列為空。
最後說明一點,WeakHashMap是執行緒安全的。
package javalearning; import java.util.WeakHashMap; public class WeakHashMapDemo { public static void main(String[] args){ WeakHashMap<String, byte[]> whm = new WeakHashMap<String, byte[]>(); String s1 = new String("s1"); String s2 = new String("s2"); String s3 = new String("s3"); whm.put(s1, new byte[100]); whm.put(s2, new byte[100]); whm.put(s3, new byte[100]); s2 = null; s3 = null; /*此時可能還未執行gc,所以可能還可以通過僅有弱引用的key找到value*/ System.out.println(whm.get("s1")); System.out.println(whm.get("s2")); System.out.println(whm.get("s3")); System.out.println("-------------------"); /*執行gc,導致僅有弱引用的key對應的entry(包括value)全部被回收*/ System.gc(); System.out.println(whm.get("s1")); System.out.println(whm.get("s2")); System.out.println(whm.get("s3")); } }
執行結果
[[email protected] [[email protected] [[email protected] ------------------- [[email protected] null null