Java參考型別
博主最近在整理Java集合架構時,在整理到WeakHashMap的時候,覺得有必要先闡述一下Java的參考型別,故此先整理的這篇文章,希望各位多提提意見。
閑話不多說,直接進入主題。Java中提供了4個層級的引用:強應用、軟引用、弱引用和虛引用。這四個引用定義在java.lang.ref的包下。
強引用( Final Reference)
就是指在程式碼中普遍存在的,類似Object obj = new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
強引用具備一下三個個特點:
1. 強引用可以直接存取目標對象;2. 強引用鎖指向的對象在任何時候都不會被系統回收。JVM寧願拋出OOM異常也不回收強引用所指向的對象;3. 強應用可能導致記憶體泄露;
整個FinalReference類的定義如下(有些API中並沒有加入FinalReference類的說明,只能看源碼了):
package java.lang.ref;/* Final references, used to implement finalization */class FinalReference extends Reference { public FinalReference(T referent, ReferenceQueue q) { super(referent, q); }}
從類定義中可以看出,只有一個建構函式,根據所給的對象的應用和應用隊列構造一個強引用。
軟引用(Soft Reference)
是用來描述一些還有用但並非必須的對象。對於軟引用關聯著的對象,在系統將要發生記憶體溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會拋出記憶體溢出異常。
對於軟引用關聯著的對象,如果記憶體充足,則記憶體回收行程不會回收該對象,如果記憶體不夠了,就會回收這些對象的記憶體。在 JDK 1.2 之後,提供了 SoftReference 類來實現軟引用。軟引用可用來實現記憶體敏感的快取。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被記憶體回收行程回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用隊列中。
案例1:
package collections.ref;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.lang.ref.SoftReference;public class SoftRefTest{ private static ReferenceQueue softQueue = new ReferenceQueue<>(); public static class MyObject{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("MyObject's finalize called"); } @Override public String toString() { return "I am MyObject"; } } public static class CheckRefQueue implements Runnable { Reference obj = null; @Override public void run() { try { obj = (Reference)softQueue.remove(); } catch (InterruptedException e) { e.printStackTrace(); } if(obj != null) { System.out.println("Object for SoftReference is "+obj.get()); } } } public static void main(String[] args) { MyObject object = new MyObject(); SoftReference softRef = new SoftReference<>(object,softQueue); new Thread(new CheckRefQueue()).start(); object = null; //刪除強引用 System.gc(); System.out.println("After GC: Soft Get= "+softRef.get()); System.out.println("分配大塊記憶體"); byte[] b = new byte[5*1024*928]; System.out.println("After new byte[]:Soft Get= "+softRef.get()); System.gc(); }}
運行參數1:
-Xmx5M
運行結果1:
After GC: Soft Get= I am MyObject分配大塊記憶體MyObject's finalize calledObject for SoftReference is nullAfter new byte[]:Soft Get= null
運行參數2:
-Xmx5M -XX:PrintGCDetails
運行結果2:
[GC [PSYoungGen: 680K->504K(2560K)] 680K->512K(6144K), 0.0040658 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 8K->482K(3584K)] 512K->482K(6144K) [PSPermGen: 2491K->2490K(21504K)], 0.0188479 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] After GC: Soft Get= I am MyObject分配大塊記憶體[GC [PSYoungGen: 123K->64K(2560K)] 605K->546K(7680K), 0.0004285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC [PSYoungGen: 64K->64K(2560K)] 546K->546K(7680K), 0.0003019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 64K->0K(2560K)] [ParOldGen: 482K->482K(4608K)] 546K->482K(7168K) [PSPermGen: 2493K->2493K(21504K)], 0.0094748 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC [PSYoungGen: 0K->0K(2560K)] 482K->482K(7680K), 0.0003759 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 482K->472K(5120K)] 482K->472K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0101017 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] MyObject's finalize calledObject for SoftReference is nullAfter new byte[]:Soft Get= null[GC [PSYoungGen: 122K->32K(2560K)] 5235K->5144K(7680K), 0.0004806 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5144K->5112K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0136270 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] Heap PSYoungGen total 2560K, used 20K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000) eden space 2048K, 1% used [0x00000000ffd00000,0x00000000ffd05250,0x00000000fff00000) from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 5120K, used 5112K [0x00000000ff800000, 0x00000000ffd00000, 0x00000000ffd00000) object space 5120K, 99% used [0x00000000ff800000,0x00000000ffcfe188,0x00000000ffd00000) PSPermGen total 21504K, used 2500K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000) object space 21504K, 11% used [0x00000000fa600000,0x00000000fa871190,0x00000000fbb00000)
加入 -XX:PrintGCDetails參數運行可以更形象的看到GC回收的細節。
這個案例1中,首先構造MyObject對象,並將其賦值給object變數,構成強引用。然後使用SoftReference構造這個MyObject對象的軟引用softRef,並註冊到softQueue引用隊列。當softRef被回收時,會被加入softQueue隊列。設定obj=null,刪除這個強引用,因此,系統內對MyObject對象的引用只剩下軟引用。此時,顯示調用GC,通過軟引用的get()方法,取得MyObject對象的引用,發現對象並未被回收,這說明GC在記憶體充足的情況下,不會回收軟引用對象。
接著,請求一塊大的堆空間5*1024*928,這個操作會使系統堆記憶體使用量緊張,從而產生新一輪的GC。在這次GC後,softRef.get()不再返回MyObject對象,而是返回null,說明在系統記憶體緊張的情況下,軟引用被回收。軟引用被回收時,會被加入註冊的引用隊列。
如果將上面案例中的數組再改大點,比如5*1024*1024,就會拋出OOM異常:
After GC: Soft Get= I am MyObject分配大塊記憶體MyObject's finalize calledObject for SoftReference is nullException in thread "main" java.lang.OutOfMemoryError: Java heap space at collections.ref.SoftRefTest.main(SoftRefTest.java:58)
軟引用主要應用於記憶體敏感的快取,在android系統中經常使用到。一般情況下,Android應用會用到大量的預設圖片,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取檔案需要硬體操作,速度較慢,會導致效能較低。所以我們考慮將圖片緩衝起來,需要的時候直接從記憶體中讀取。但是,由於圖片佔用記憶體空間比較大,緩衝很多圖片需要很多的記憶體,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟引用技術來避免這個問題發生。SoftReference可以解決oom的問題,每一個對象通過軟引用進行執行個體化,這個對象就以cache的形式儲存起來,當再次調用這個對象時,那麼直接通過軟引用中的get()方法,就可以得到對象中中的資源資料,這樣就沒必要再次進行讀取了,直接從cache中就可以讀取得到,當記憶體將要發生OOM的時候,GC會迅速把所有的軟引用清除,防止oom發生。
案例2:
public class BitMapManager { private Map> imageCache = new HashMap>(); //儲存Bitmap的軟引用到HashMap public void saveBitmapToCache(String path) { // 強引用的Bitmap對象 Bitmap bitmap = BitmapFactory.decodeFile(path); // 軟引用的Bitmap對象 SoftReference softBitmap = new SoftReference(bitmap); // 添加該對象到Map中使其緩衝 imageCache.put(path, softBitmap); // 使用完後手動將位元影像對象置null bitmap = null; } public Bitmap getBitmapByPath(String path) { // 從緩衝中取軟引用的Bitmap對象 SoftReference softBitmap = imageCache.get(path); // 判斷是否存在軟引用 if (softBitmap == null) { return null; } // 取出Bitmap對象,如果由於記憶體不足Bitmap被回收,將取得空 Bitmap bitmap = softBitmap.get(); return bitmap; }}
弱引用(Weak Reference)
用來描述非必須的對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發送之前。當垃圾收集器工作時,無論當前記憶體是否足夠,都會回收掉只被弱引用關聯的對象。一旦一個弱引用對象被記憶體回收行程回收,便會加入到一個註冊引用隊列中。
我們略微修改一下案例1的代碼,如下:
package collections.ref;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.lang.ref.WeakReference;public class WeakRefTest{ private static ReferenceQueue weakQueue = new ReferenceQueue<>(); public static class MyObject{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("MyObject's finalize called"); } @Override public String toString() { return "I am MyObject"; } } public static class CheckRefQueue implements Runnable { Reference obj = null; @Override public void run() { try { obj = (Reference)weakQueue.remove(); } catch (InterruptedException e) { e.printStackTrace(); } if(obj != null) { System.out.println("刪除的弱引用為:"+obj+" but擷取弱引用的對象obj.get()="+obj.get()); } } } public static void main(String[] args) { MyObject object = new MyObject(); Reference weakRef = new WeakReference<>(object,weakQueue); System.out.println("建立的弱引用為:"+weakRef); new Thread(new CheckRefQueue()).start(); object = null; System.out.println("Before GC: Weak Get= "+weakRef.get()); System.gc(); System.out.println("After GC: Weak Get= "+weakRef.get()); }}
不加參數運行結果:
建立的弱引用為:[email protected]Before GC: Weak Get= I am MyObjectAfter GC: Weak Get= nullMyObject's finalize called刪除的弱引用為:[email protected] but擷取弱引用的對象obj.get()=null
可以看到,在GC之前,弱引用對象並未被記憶體回收行程發現,因此通過 weakRef.get()可以擷取對應的對象引用。但是只要進行記憶體回收,弱引用一旦被發現,便會立即被回收,並加入註冊引用隊列中。此時再試圖通過weakRef.get()擷取對象的引用就會失敗。
弱引用的相關實際案例可以參考WeakHashMap,博主會在近期整理出相關文檔。等不及的小夥伴可以自行度娘之。
軟引用、弱引用都非常適合來儲存那些可有可無的快取資料。如果這麼做,當系統記憶體不足時,這些快取資料會被回收,不會導致記憶體溢出。而當記憶體資源充足時,這些快取資料又可以存在相當長的時間,從而起來加速系統的作用。
虛引用(Phantom Reference)
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種參考關聯性。一個持有虛引用的對象,和沒有引用幾乎是一樣的,隨時都有可能被記憶體回收行程回收。當試圖通過虛引用的get()方法取得強引用時,總是會失敗。並且,虛引用必須和引用隊列一起使用,它的作用在於跟蹤記憶體回收過程。
虛引用中get方法的實現如下:
public T get() { return null; }
可以看到永遠返回null.
我們再來修改一下案例1的代碼:
package collections.ref;import java.lang.ref.PhantomReference;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.util.concurrent.TimeUnit;public class PhantomRefTest{ private static ReferenceQueue phanQueue = new ReferenceQueue<>(); public static class MyObject{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("MyObject's finalize called"); } @Override public String toString() { return "I am MyObject"; } } public static class CheckRefQueue implements Runnable { Reference obj = null; @Override public void run() { try { obj = (Reference)phanQueue.remove(); System.out.println("刪除的虛引用為:"+obj+" but擷取虛引用的對象obj.get()="+obj.get()); System.exit(0); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { MyObject object = new MyObject(); Reference phanRef = new PhantomReference<>(object,phanQueue); System.out.println("建立的虛引用為:"+phanRef); new Thread(new CheckRefQueue()).start(); object = null; TimeUnit.SECONDS.sleep(1); int i =1; while(true) { System.out.println("第"+i+++"次gc"); System.gc(); TimeUnit.SECONDS.sleep(1); } }}
運行結果:
建立的虛引用為:[email protected]第1次gcMyObject's finalize called第2次gc刪除的虛引用為:[email protected] but擷取虛引用的對象obj.get()=null
可以看到,再經過一次GC之後,系統找到了垃圾對象,並調用finalize()方法回收記憶體,但沒有立即加入回收隊列。第二次GC時,該對象真正被GC清楚,此時,加入虛引用隊列。
虛引用的最大作用在於跟蹤對象回收,清理被銷毀對象的相關資源。
通常當對象不被使用時,重載該對象的類的finalize方法可以回收對象的資源。但是如果使用不慎,會使得對象複活,譬如這麼編寫finalize方法:
public class Test{ public static Test obj; @Override protected void finalize() throws Throwable{ super.finalize(); obj = this; }}
對上面這個類Test中obj = new Test();然後obj=null;之後調用System.gc()企圖銷毀對象,但是很抱歉,不管你調用多少次System.gc()都沒有什麼用,除非你在下面的代碼中再就obj=null;這樣才能回收對象,這是因為JVM對某一個對象至多隻執行一次被重寫的finalize方法。
上面的小片段說明重寫finalize的方法並不是很靠譜,可以使用虛引用來清理對象所佔用的資源。
如下代碼所示:
public class PhantomRefTest2{ private static ReferenceQueue phanQueue = new ReferenceQueue<>(); private static Map,String> map = new HashMap<>(); public static class MyObject{ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("MyObject's finalize called"); } @Override public String toString() { return "I am MyObject"; } } public static class CheckRefQueue implements Runnable { Reference obj = null; @Override public void run() { try { obj = (Reference)phanQueue.remove(); Object value = map.get(obj); System.out.println("clean resource:"+value); map.remove(obj); System.out.println("刪除的虛引用為:"+obj+" but擷取虛引用的對象obj.get()="+obj.get()); System.exit(0); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { MyObject object = new MyObject(); Reference phanRef = new PhantomReference<>(object,phanQueue); System.out.println("建立的虛引用為:"+phanRef); new Thread(new CheckRefQueue()).start(); map.put(phanRef, "Some Resources"); object = null; TimeUnit.SECONDS.sleep(1); int i =1; while(true) { System.out.println("第"+i+++"次gc"); System.gc(); TimeUnit.SECONDS.sleep(1); } }}
運行結果:
建立的虛引用為:[email protected]第1次gcMyObject's finalize called第2次gcclean resource:Some Resources刪除的虛引用為:[email protected] but擷取虛引用的對象obj.get()=null