1. 程式人生 > >Java中的強引用,軟引用,弱引用和虛引用

Java中的強引用,軟引用,弱引用和虛引用

從JDK1.2版本開始,把物件的引用分為四種級別,從而使程式能更加靈活的控制物件的生命週期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。

一、強引用
如果一個物件具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空 間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。

例如:

  1. Object o=new Object();       
  2. Object o1=o;     
 上面程式碼中第一句是在heap堆中建立新的Object物件通過o引用這個物件,第二句是通過o建立o1到new Object()這個heap堆中的物件的引用,這兩個引用都是強引用.只要存在對heap中物件的引用,gc就不會收集該物件.如果通過如下程式碼:
  1. o=
    null;       
  2. o1=null;   

如果顯式地設定o和o1為null,或超出範圍,則gc認為該物件不存在引用,這時就可以收集它了。可以收集並不等於就一會被收集,什麼時候收集這要取決於gc的演算法,這要就帶來很多不確定性。例如你就想指定一個物件,希望下次gc執行時把它收集了,那就沒辦法了,有了其他的三種引用就可以做到了。其他三種引用在不妨礙gc收集的情況下,可以做簡單的互動。

heap中物件有強可及物件、軟可及物件、弱可及物件、虛可及物件和不可到達物件。應用的強弱順序是強、軟、弱、和虛。對於物件是屬於哪種可及的物件,由他的最強的引用決定。如下:

  1. String abc=new
     String("abc");  //1    
  2. SoftReference<String> abcSoftRef=new SoftReference<String>(abc);  //2    
  3. WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3    
  4. abc=null//4    
  5. abcSoftRef.clear();//5 
  上面的程式碼中:

    第一行在heap對中建立內容為“abc”的物件,並建立abc到該物件的強引用,該物件是強可及的。

    第二行和第三行分別建立對heap中物件的軟引用和弱引用,此時heap中的物件仍是強可及的。

    第四行之後heap中物件不再是強可及的,變成軟可及的。同樣第五行執行之後變成弱可及的。


二、軟引用(SoftReference)

如果一個物件只具有軟引用,那就類似於可有可物的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。
軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。

軟引用是主要用於記憶體敏感的快取記憶體。在jvm報告記憶體不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的物件,可能解決記憶體吃緊問題,避免記憶體溢位。什麼時候會被收集取決於gc的演算法和gc執行時可用記憶體的大小。當gc決定要收集軟引用是執行以下過程,以上面的abcSoftRef為例:

    1 首先將abcSoftRef的referent設定為null,不再引用heap中的new String("abc")物件。

    2 將heap中的new String("abc")物件設定為可結束的(finalizable)。

    3 當heap中的new String("abc")物件的finalize()方法被執行而且該物件佔用的記憶體被釋放, abcSoftRef被新增到它的ReferenceQueue中。

   注:對ReferenceQueue軟引用和弱引用可以有可無,但是虛引用必須有,參見:

  1. Reference(T paramT, ReferenceQueue<? super T>paramReferenceQueue)     
 

被 Soft Reference 指到的物件,即使沒有任何 Direct Reference,也不會被清除。一直要到 JVM 記憶體不足且 沒有 Direct Reference 時才會清除,SoftReference 是用來設計 object-cache 之用的。如此一來 SoftReference 不但可以把物件 cache 起來,也不會造成記憶體不足的錯誤 (OutOfMemoryError)。我覺得 Soft Reference 也適合拿來實作 pooling 的技巧。 

  1.  A obj = new A();    
  2. Refenrence sr = new SoftReference(obj);    
  3. //引用時 
  4. if(sr!=null){    
  5.     obj = sr.get();    
  6. }else{    
  7.     obj = new A();    
  8.     sr = new SoftReference(obj);    
  9. }  

三、弱引用(WeakReference)
如果一個物件只具有弱引用,那就類似於可有可物的生活用品。弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它 所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。
弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。

當gc碰到弱可及物件,並釋放abcWeakRef的引用,收集該物件。但是gc可能需要對此運用才能找到該弱可及物件。通過如下程式碼可以了明瞭的看出它的作用:

  1. String abc=new String("abc");       
  2. WeakReference<String> abcWeakRef = new WeakReference<String>(abc);       
  3. abc=null;       
  4. System.out.println("before gc: "+abcWeakRef.get());       
  5. System.gc();       
  6. System.out.println("after gc: "+abcWeakRef.get());     
 

執行結果:    

before gc: abc    

after gc: null   

gc收集弱可及物件的執行過程和軟可及一樣,只是gc不會根據記憶體情況來決定是不是收集該物件。

如果你希望能隨時取得某物件的資訊,但又不想影響此物件的垃圾收集,那麼你應該用 Weak Reference 來記住此物件,而不是用一般的 reference。

  1. A obj = new A();    
  2.     WeakReference wr = new WeakReference(obj);    
  3.     obj = null;    
  4. //等待一段時間,obj物件就會被垃圾回收
  5.   ...    
  6. if (wr.get()==null) {    
  7.   System.out.println("obj 已經被清除了 ");    
  8.   } else {    
  9.   System.out.println("obj 尚未被清除,其資訊是 "+obj.toString());   
  10.   }   
  11.   ...   
  12. }  
 在此例中,透過 get() 可以取得此 Reference 的所指到的物件,如果返回值為 null 的話,代表此物件已經被清除。 

這類的技巧,在設計 Optimizer 或 Debugger 這類的程式時常會用到,因為這類程式需要取得某物件的資訊,但是不可以 影響此物件的垃圾收集。


四、虛引用(PhantomReference)
"虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要用來跟蹤物件被垃圾回收的活動。

虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是 否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。程式如果發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。 

建立虛引用之後通過get方法返回結果始終為null,通過原始碼你會發現,虛引用通向會把引用的物件寫進referent,只是get方法返回結果為null.先看一下和gc互動的過程在說一下他的作用.

  1 不把referent設定為null, 直接把heap中的new String("abc")物件設定為可結束的(finalizable).

  2 與軟引用和弱引用不同, 先把PhantomRefrence物件新增到它的ReferenceQueue中.然後在釋放虛可及的物件. 

   你會發現在收集heap中的new String("abc")物件之前,你就可以做一些其他的事情.通過以下程式碼可以瞭解他的作用.

  1. import java.lang.ref.PhantomReference;       
  2. import java.lang.ref.Reference;       
  3. import java.lang.ref.ReferenceQueue;       
  4. import java.lang.reflect.Field;       
  5. publicclass Test {       
  6. publicstaticboolean isRun = true;       
  7. publicstaticvoid main(String[] args) throws Exception {       
  8.         String abc = new String("abc");       
  9.         System.out.println(abc.getClass() + "@" + abc.hashCode());       
  10. final ReferenceQueue referenceQueue = new ReferenceQueue<String>();       
  11. new Thread() {       
  12. publicvoid run() {       
  13. while (isRun) {       
  14.                     Object o = referenceQueue.poll();       
  15. if (o != null) {       
  16. try {       
  17.                             Field rereferent = Reference.class
  18.                                     .getDeclaredField("referent");       
  19.                             rereferent.setAccessible(true);       
  20.                             Object result = rereferent.get(o);       
  21.                             System.out.println("gc will collect:"
  22.                                     + result.getClass() + "@"
  23.                                     + result.hashCode());       
  24.                         } catch (Exception e) {       
  25.                             e.printStackTrace();       
  26.                         }       
  27.                     }       
  28.                 }       
  29.             }       
  30.         }.start();       
  31.         PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,       
  32.                 referenceQueue);       
  33.         abc = null;       
  34.         Thread.currentThread().sleep(3000);       
  35.         System.gc();       
  36.         Thread.currentThread().sleep(3000);       
  37.         isRun = false;       
  38.     }       
  39. }   
  結果為 class java[email protected]