1. 程式人生 > >java四種引用方式

java四種引用方式

java記憶體管理分為記憶體分配和記憶體回收,都不需要程式設計師負責,垃圾回收的機制主要是看物件是否有引用指向該物件。

java物件的引用包括
  強引用,軟引用,弱引用,虛引用

Java中提供這四種引用型別主要有兩個目的:

第一是可以讓程式設計師通過程式碼的方式決定某些物件的生命週期;

第二是有利於JVM進行垃圾回收。

下面來闡述一下這四種類型引用的概念:

1.強引用

 是指建立一個物件並把這個物件賦給一個引用變數。

比如:

Object object =new Object();

String str ="hello";

 強引用有引用變數指向時永遠不會被垃圾回收,JVM寧願丟擲OutOfMemory錯誤也不會回收這種物件。

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">public class Main {  
  2.     public static void main(String[] args) {  
  3.         new Main().fun1();  
  4.     }  
  5.     public void fun1() {  
  6.         Object object = new Object();  
  7.         Object[] objArr = new Object[1000];  
  8.  }  

當執行至Object[] objArr = new Object[1000];這句時,如果記憶體不足,JVM會丟擲OOM錯誤也不會回收object指向的物件。不過要注意的是,當fun1執行完之後,object和objArr都已經不存在了,所以它們指向的物件都會被JVM回收。

  如果想中斷強引用和某個物件之間的關聯,可以顯示地將引用賦值為null,這樣一來的話,JVM在合適的時間就會回收該物件。

比如Vector類的clear方法中就是通過將引用賦值為null來實現清理工作的:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">/** 
  2.      * Removes the element at the specified position in this Vector. 
  3.      * Shifts any subsequent elements to the left (subtracts one from their 
  4.      * indices).  Returns the element that was removed from the Vector. 
  5.      * 
  6.      * @throws ArrayIndexOutOfBoundsException if the index is out of range 
  7.      *         ({@code index < 0 || index >= size()}) 
  8.      * @param index the index of the element to be removed 
  9.      * @return element that was removed 
  10.      * @since 1.2 
  11.      */  
  12.     public synchronized E remove(int index) {  
  13.     modCount++;  
  14.     if (index >= elementCount)  
  15.         throw new ArrayIndexOutOfBoundsException(index);  
  16.     Object oldValue = elementData[index];  
  17.     int numMoved = elementCount - index - 1;  
  18.     if (numMoved > 0)  
  19.         System.arraycopy(elementData, index+1, elementData, index,  
  20.                  numMoved);  
  21.     elementData[--elementCount] = null; // Let gc do its work  
  22.     return (E)oldValue;  
  23.     }  

2.軟引用(SoftReference)

如果一個物件具有軟引用,記憶體空間足夠,垃圾回收器就不會回收它;

如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。

軟引用可用來實現記憶體敏感的快取記憶體,比如網頁快取、圖片快取等。使用軟引用能防止記憶體洩露,增強程式的健壯性。   
SoftReference的特點是它的一個例項儲存對一個Java物件的軟引用, 該軟引用的存在不妨礙垃圾收集執行緒對該Java物件的回收。

也就是說,一旦SoftReference儲存了對一個Java物件的軟引用後,在垃圾執行緒對 這個Java物件回收前,SoftReference類所提供的get()方法返回Java物件的強引用。

另外,一旦垃圾執行緒回收該Java物件之 後,get()方法將返回null。

舉個栗子:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">MyObject aRef = new  MyObject();  
  2. SoftReference aSoftRef=new SoftReference(aRef);  

此時,對於這個MyObject物件,有兩個引用路徑,一個是來自SoftReference物件的軟引用,一個來自變數aReference的強引用,所以這個MyObject物件是強可及物件。

隨即,我們可以結束aReference對這個MyObject例項的強引用:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">aRef = null;  

此後,這個MyObject物件成為了軟引用物件。如果垃圾收集執行緒進行記憶體垃圾收集,並不會因為有一個SoftReference對該物件的引用而始終保留該物件。
Java虛擬機器的垃圾收集執行緒對軟可及物件和其他一般Java物件進行了區別對待:軟可及物件的清理是由垃圾收集執行緒根據其特定演算法按照記憶體需求決定的。
也就是說,垃圾收集執行緒會在虛擬機器丟擲OutOfMemoryError之前回收軟可及物件,而且虛擬機器會盡可能優先回收長時間閒置不用的軟可及物件,對那些剛剛構建的或剛剛使用過的“新”軟可反物件會被虛擬機器儘可能保留。在回收這些物件之前,我們可以通過:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">MyObject anotherRef=(MyObject)aSoftRef.get();  

重新獲得對該例項的強引用。而回收之後,呼叫get()方法就只能得到null了。
使用ReferenceQueue清除失去了軟引用物件的SoftReference:

作為一個Java物件,SoftReference物件除了具有儲存軟引用的特殊性之外,也具有Java物件的一般性。所以,當軟可及物件被回收之後,雖然這個SoftReference物件的get()方法返回null,但這個SoftReference物件已經不再具有存在的價值,需要一個適當的清除機制,避免大量SoftReference物件帶來的記憶體洩漏。在java.lang.ref包裡還提供了ReferenceQueue。如果在建立SoftReference物件的時候,使用了一個ReferenceQueue物件作為引數提供給SoftReference的構造方法,如:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">ReferenceQueue queue = new  ReferenceQueue();  
  2. SoftReference  ref=new  SoftReference(aMyObject, queue);  

那麼當這個SoftReference所軟引用的aMyOhject被垃圾收集器回收的同時,ref所強引用的SoftReference物件被列入ReferenceQueue。也就是說,ReferenceQueue中儲存的物件是Reference物件,而且是已經失去了它所軟引用的物件的Reference物件。另外從ReferenceQueue這個名字也可以看出,它是一個佇列,當我們呼叫它的poll()方法的時候,如果這個佇列中不是空佇列,那麼將返回佇列前面的那個Reference物件。

在任何時候,我們都可以呼叫ReferenceQueue的poll()方法來檢查是否有它所關心的非強可及物件被回收。如果佇列為空,將返回一個null,否則該方法返回佇列中前面的一個Reference物件。利用這個方法,我們可以檢查哪個SoftReference所軟引用的物件已經被回收。於是我們可以把這些失去所軟引用的物件的SoftReference物件清除掉。常用的方式為:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">SoftReference ref = null;  
  2. while ((ref = (EmployeeRef) q.poll()) != null) {  
  3.     // 清除ref  
  4. }  

3.弱引用(WeakReference)

  弱引用也是用來描述非必需物件的,當JVM進行垃圾回收時,無論記憶體是否充足,都會回收被弱引用關聯的物件。在java中,用java.lang.ref.WeakReference類來表示。下面是使用示例:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">public class test {  
  2.     public static void main(String[] args) {  
  3.         WeakReference<People>reference=new WeakReference<People>(new People("zhouqian",20));  
  4.         System.out.println(reference.get());  
  5.         System.gc();//通知GVM回收資源  
  6.         System.out.println(reference.get());  
  7.     }  
  8. }  
  9. class People{  
  10.     public String name;  
  11.     public int age;  
  12.     public People(String name,int age) {  
  13.         this.name=name;  
  14.         this.age=age;  
  15.     }  
  16.     @Override  
  17.     public String toString() {  
  18.         return "[name:"+name+",age:"+age+"]";  
  19.     }  
  20. }  
  21. 輸出結果:  

[name:zhouqian,age:20]
null
第二個輸出結果是null,這說明只要JVM進行垃圾回收,被弱引用關聯的物件必定會被回收掉。不過要注意的是,這裡所說的被弱引用關聯的物件是指只有弱引用與之關聯,如果存在強引用同時與之關聯,則進行垃圾回收時也不會回收該物件(軟引用也是如此)。

比如:將程式碼做一點小更改:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">package yinyong;  
  2. import java.lang.ref.WeakReference;  
  3. public class test {  
  4.     public static void main(String[] args) {  
  5.         People people=new People("zhouqian",20);  
  6.         WeakReference<People>reference=new WeakReference<People>(people);//<span style="color:#FF0000;">關聯強引用</span>  
  7.         System.out.println(reference.get());  
  8.         System.gc();  
  9.         System.out.println(reference.get());  
  10.     }  
  11. }  
  12. class People{  
  13.     public String name;  
  14.     public int age;  
  15.     public People(String name,int age) {  
  16.         this.name=name;  
  17.         this.age=age;  
  18.     }  
  19.     @Override  
  20.     public String toString() {  
  21.         return "[name:"+name+",age:"+age+"]";  
  22.     }  
  23. }//結果發生了很大的變化  
  24. [name:zhouqian,age:20]  
  25. [name:zhouqian,age:20]  

弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被JVM回收,這個軟引用就會被加入到與之關聯的引用佇列中。

4.虛引用(PhantomReference)

  虛引用和前面的軟引用、弱引用不同,它並不影響物件的生命週期。在java中用java.lang.ref.PhantomReference類表示。如果一個物件與虛引用關聯,則跟沒有引用與之關聯一樣,在任何時候都可能被垃圾回收器回收。

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

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">import java.lang.ref.PhantomReference;  
  2. import java.lang.ref.ReferenceQueue;  
  3. public class Main {  
  4.     public static void main(String[] args) {  
  5.         ReferenceQueue<String> queue = new ReferenceQueue<String>();  
  6.         PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue);  
  7.         System.out.println(pr.get());  
  8.     }  
  9. }  

軟引用和弱引用
    對於強引用,我們平時在編寫程式碼時經常會用到。而對於其他三種類型的引用,使用得最多的就是軟引用和弱引用,這2種既有相似之處又有區別。它們都是用來描述非必需物件的,但是被軟引用關聯的物件只有在記憶體不足時才會被回收,而被弱引用關聯的物件在JVM進行垃圾回收時總會被回收。

    在SoftReference類中,有三個方法,兩個構造方法和一個get方法(WekReference類似):

兩個構造方法:

 在CODE上檢視程式碼片派生到我的程式碼片

  1. <pre name="code" class="java">public SoftReference(T referent) {  
  2.     super(referent);  
  3.     this.timestamp = clock;  
  4.     }  
  5. public SoftReference(T referent, ReferenceQueue<? super T> q) {  
  6.     super(referent, q);  
  7.     this.timestamp = clock;  
  8.     }  

        get方法用來獲取與軟引用關聯的物件的引用,如果該物件被回收了,則返回null。

   在使用軟引用和弱引用的時候,我們可以顯示地通過System.gc()來通知JVM進行垃圾回收,但是要注意的是,雖然發出了通知,JVM不一定會立刻執行,也就是說這句是無法確保此時JVM一定會進行垃圾回收的。

物件可及性的判斷

在很多時候,一個物件並不是從根集直接引用的,而是一個物件被其他物件引用,甚至同時被幾個物件所引用,從而構成一個以根集為頂的樹形結構。如圖2所示

    在這個樹形的引用鏈中,箭頭的方向代表了引用的方向,所指向的物件是被引用物件。由圖可以看出,從根集到一個物件可以由很多條路徑。比如到達物件5的路徑就有①-⑤,③-⑦兩條路徑。由此帶來了一個問題,那就是某個物件的可及性如何判斷:

◆單條引用路徑可及性判斷:在這條路徑中,最弱的一個引用決定物件的可及性。

◆多條引用路徑可及性判斷:幾條路徑中,最強的一條的引用決定物件的可及性。

    比如,我們假設圖2中引用①和③為強引用,⑤為軟引用,⑦為弱引用,對於物件5按照這兩個判斷原則,路徑①-⑤取最弱的引用⑤,因此該路徑對物件5的引用為軟引用。同樣,③-⑦為弱引用。在這兩條路徑之間取最強的引用,於是物件5是一個軟可及物件

如何利用軟引用和弱引用解決OOM問題

  前面講了關於軟引用和弱引用相關的基礎知識,那麼到底如何利用它們來優化程式效能,從而避免OOM的問題呢?

  下面舉個例子,假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬碟讀取,則會嚴重影響效能,但是如果全部載入到記憶體當中,又有可能造成記憶體溢位,此時使用軟引用可以解決這個問題。

  設計思路是:用一個HashMap來儲存圖片的路徑 和 相應圖片物件關聯的軟引用之間的對映關係,在記憶體不足時,JVM會自動回收這些快取圖片物件所佔用的空間,從而有效地避免了OOM的問題。在Android開發中對於大量圖片下載會經常用到。

3.使用軟引用構建敏感資料的快取

3.1 為什麼需要使用軟引用

   首先,我們看一個僱員資訊查詢系統的例項。我們將使用一個Java語言實現的僱員資訊查詢系統查詢儲存在磁碟檔案或者資料庫中的僱員人事檔案資訊。作為一個使用者,我們完全有可能需要回頭去檢視幾分鐘甚至幾秒鐘前檢視過的僱員檔案資訊(同樣,我們在瀏覽WEB頁面的時候也經常會使用“後退”按鈕)。這時我們通常會有兩種程式實現方式:一種是把過去檢視過的僱員資訊儲存在記憶體中,每一個儲存了僱員檔案資訊的Java物件的生命週期貫穿整個應用程式始終;另一種是當用戶開始檢視其他僱員的檔案資訊的時候,把儲存了當前所檢視的僱員檔案資訊的Java物件結束引用,使得垃圾收集執行緒可以回收其所佔用的記憶體空間,當用戶再次需要瀏覽該僱員的檔案資訊的時候,重新構建該僱員的資訊。很顯然,第一種實現方法將造成大量的記憶體浪費,而第二種實現的缺陷在於即使垃圾收集執行緒還沒有進行垃圾收集,包含僱員檔案資訊的物件仍然完好地儲存在記憶體中,應用程式也要重新構建一個物件。我們知道,訪問磁碟檔案、訪問網路資源、查詢資料庫等操作都是影響應用程式執行效能的重要因素,如果能重新獲取那些尚未被回收的Java物件的引用,必將減少不必要的訪問,大大提高程式的執行速度。

3.2 如果使用軟引用

SoftReference的特點是它的一個例項儲存對一個Java物件的軟引用,該軟引用的存在不妨礙垃圾收集執行緒對該Java物件的回收。也就是說,一旦SoftReference儲存了對一個Java物件的軟引用後,在垃圾執行緒對這個Java物件回收前,SoftReference類所提供的get()方法返回Java物件的強引用。另外,一旦垃圾執行緒回收該Java物件之後,get()方法將返回null。

看下面程式碼:

MyObject aRef = new  MyObject();

SoftReference aSoftRef=new SoftReference(aRef);

    此時,對於這個MyObject物件,有兩個引用路徑,一個是來自SoftReference物件的軟引用,一個來自變數aReference的強引用,所以這個MyObject物件是強可及物件。

隨即,我們可以結束aReference對這個MyObject例項的強引用:

aRef = null;

此後,這個MyObject物件成為了軟可及物件。如果垃圾收集執行緒進行記憶體垃圾收集,並不會因為有一個SoftReference對該物件的引用而始終保留該物件。Java虛擬機器的垃圾收集執行緒對軟可及物件和其他一般Java物件進行了區別對待:軟可及物件的清理是由垃圾收集執行緒根據其特定演算法按照記憶體需求決定的。也就是說,垃圾收集執行緒會在虛擬機器丟擲OutOfMemoryError之前回收軟可及物件,而且虛擬機器會盡可能優先回收長時間閒置不用的軟可及物件,對那些剛剛構建的或剛剛使用過的“新”軟可反物件會被虛擬機器儘可能保留。在回收這些物件之前,我們可以通過:

MyObject anotherRef=(MyObject)aSoftRef.get();

    重新獲得對該例項的強引用。而回收之後,呼叫get()方法就只能得到null了。

3.3 使用ReferenceQueue清除失去了軟引用物件的SoftReference

作為一個Java物件,SoftReference物件除了具有儲存軟引用的特殊性之外,也具有Java物件的一般性。所以,當軟可及物件被回收之後,雖然這個SoftReference物件的get()方法返回null,但這個SoftReference物件已經不再具有存在的價值,需要一個適當的清除機制,避免大量SoftReference物件帶來的記憶體洩漏。在java.lang.ref包裡還提供了ReferenceQueue。如果在建立SoftReference物件的時候,使用了一個ReferenceQueue物件作為引數提供給SoftReference的構造方法,如:

ReferenceQueue queue = new  ReferenceQueue();

SoftReference  ref=new  SoftReference(aMyObject, queue);

    那麼當這個SoftReference所軟引用的aMyOhject被垃圾收集器回收的同時,ref所強引用的SoftReference物件被列入ReferenceQueue。也就是說,ReferenceQueue中儲存的物件是Reference物件,而且是已經失去了它所軟引用的物件的Reference物件。另外從ReferenceQueue這個名字也可以看出,它是一個佇列,當我們呼叫它的poll()方法的時候,如果這個佇列中不是空佇列,那麼將返回佇列前面的那個Reference物件。

在任何時候,我們都可以呼叫ReferenceQueue的poll()方法來檢查是否有它所關心的非強可及物件被回收。如果佇列為空,將返回一個null,否則該方法返回佇列中前面的一個Reference物件。利用這個方法,我們可以檢查哪個SoftReference所軟引用的物件已經被回收。於是我們可以把這些失去所軟引用的物件的SoftReference物件清除掉。常用的方式為:

SoftReference ref = null;

while ((ref = (EmployeeRef) q.poll()) != null) {

    // 清除ref

}

理解了ReferenceQueue的工作機制之後,我們就可以開始構造一個Java物件的快取記憶體器了。
 

3.4通過軟可及物件重獲方法實現Java物件的快取記憶體

    利用Java2平臺垃圾收集機制的特性以及前述的垃圾物件重獲方法,我們通過一個僱員資訊查詢系統的小例子來說明如何構建一種快取記憶體器來避免重複構建同一個物件帶來的效能損失。我們將一個僱員的檔案資訊定義為一個Employee類:

publicclass Employee {

    private String id;// 僱員的標識號碼

    private String name;// 僱員姓名

    private String department;// 該僱員所在部門

    private String Phone;// 該僱員聯絡電話

    privateintsalary;// 該僱員薪資

    private String origin;// 該僱員資訊的來源

    // 構造方法

    public Employee(String id) {

       this.id = id;

       getDataFromlnfoCenter();

    }

    // 到資料庫中取得僱員資訊

    privatevoid getDataFromlnfoCenter() {

       // 和資料庫建立連線井查詢該僱員的資訊,將查詢結果賦值

       // 給name,department,plone,salary等變數

       // 同時將origin賦值為"From DataBase"

    }

……

這個Employee類的構造方法中我們可以預見,如果每次需要查詢一個僱員的資訊。哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個例項,這是需要消耗很多時間的。下面是一個對Employee物件進行快取的快取器的定義:

import java.lang.ref.ReferenceQueue;

import java.lang.ref.SoftReference;

import java.util.Hashtable;

publicclass EmployeeCache {

    staticprivate EmployeeCache cache;// 一個Cache例項

    private Hashtable<String,EmployeeRef> employeeRefs;// 用於Chche內容的儲存

    private ReferenceQueue<Employee> q;// 垃圾Reference的佇列

    // 繼承SoftReference,使得每一個例項都具有可識別的標識。

    // 並且該標識與其在HashMap內的key相同。

    privateclass EmployeeRef extends SoftReference<Employee> {

       private String _key = "";

       public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {

           super(em, q);

           _key = em.getID();

       }

    }

    // 構建一個快取器例項

    private EmployeeCache() {

       employeeRefs = new Hashtable<String,EmployeeRef>();

       q = new ReferenceQueue<Employee>();

    }

    // 取得快取器例項

    publicstatic EmployeeCache getInstance() {

       if (cache == null) {

           cache = new EmployeeCache();

       }

       returncache;

    }

    // 以軟引用的方式對一個Employee物件的例項進行引用並儲存該引用

    privatevoid cacheEmployee(Employee em) {

       cleanCache();// 清除垃圾引用

       EmployeeRef ref = new EmployeeRef(em, q);

       employeeRefs.put(em.getID(), ref);

    }

    // 依據所指定的ID號,重新獲取相應Employee物件的例項

    public Employee getEmployee(String ID) {

       Employee em = null;

       // 快取中是否有該Employee例項的軟引用,如果有,從軟引用中取得。

       if (employeeRefs.containsKey(ID)) {

           EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);

           em = (Employee) ref.get();

       }

       // 如果沒有軟引用,或者從軟引用中得到的例項是null,重新構建一個例項,

       // 並儲存對這個新建例項的軟引用

       if (em == null) {

           em = new Employee(ID);

           System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);

           this.cacheEmployee(em);

       }

       return em;

    }

    // 清除那些所軟引用的Employee物件已經被回收的EmployeeRef物件

    privatevoid cleanCache() {

       EmployeeRef ref = null;

       while ((ref = (EmployeeRef) q.poll()) != null) {

           employeeRefs.remove(ref._key);

       }

    }

    // 清除Cache內的全部內容

    publicvoid clearCache() {

       cleanCache();

       employeeRefs.clear();

       System.gc();

       System.runFinalization();

    }

}

4.使用弱引用構建非敏感資料的快取

4.1全域性 Map 造成的記憶體洩漏

無意識物件保留最常見的原因是使用Map將元資料與臨時物件(transient object)相關聯。假定一個物件具有中等生命週期,比分配它的那個方法呼叫的生命週期長,但是比應用程式的生命週期短,如客戶機的套接字連線。需要將一些元資料與這個套接字關聯,如生成連線的使用者的標識。在建立Socket時是不知道這些資訊的,並且不能將資料新增到Socket物件上,因為不能控制 Socket 類或者它的子類。這時,典型的方法就是在一個全域性 Map 中儲存這些資訊,如下面的 SocketManager 類所示:使用一個全域性 Map 將元資料關聯到一個物件。

publicclass SocketManager {

    private Map<Socket, User> m = new HashMap<Socket, User>();

    publicvoid setUser(Socket s, User u) {

       m.put(s, u);

    }

    public User getUser(Socket s) {

       returnm.get(s);

    }

    publicvoid removeUser(Socket s) {

       m.remove(s);

    }

}

這種方法的問題是元資料的生命週期需要與套接字的生命週期掛鉤,但是除非準確地知道什麼時候程式不再需要這個套接字,並記住從 Map 中刪除相應的對映,否則,Socket 和 User 物件將會永遠留在 Map 中,遠遠超過響應了請求和關閉套接字的時間。這會阻止 Socket 和 User 物件被垃圾收集,即使應用程式不會再使用它們。這些物件留下來不受控制,很容易造成程式在長時間執行後記憶體爆滿。除了最簡單的情況,在幾乎所有情況下找出什麼時候 Socket 不再被程式使用是一件很煩人和容易出錯的任務,需要人工對記憶體進行管理。

4.2如何使用WeakHashMap

在Java集合中有一種特殊的Map型別—WeakHashMap,在這種Map中存放了鍵物件的弱引用,當一個鍵物件被垃圾回收器回收時,那麼相應的值物件的引用會從Map中刪除。WeakHashMap能夠節約儲存空間,可用來快取那些非必須存在的資料。關於Map介面的一般用法。

下面示例中MapCache類的main()方法建立了一個WeakHashMap物件,它存放了一組Key物件的弱引用,此外main()方法還建立了一個數組物件,它存放了部分Key物件的強引用。

import java.util.WeakHashMap;

class Element {

    private String ident;

    public Element(String id) {

       ident = id;

    }

    public String toString() {

       returnident;

    }

    publicint hashCode() {

       returnident.hashCode();

    }

    publicboolean equals(Object obj) {

       return obj instanceof Element && ident.equals(((Element) obj).ident);

    }

    protectedvoid finalize(){

       System.out.println("Finalizing "+getClass().getSimpleName()+" "+ident);

    }

}

class Key extends Element{

    public Key(String id){

       super(id);

    }

}

class Value extends Element{

    public Value (String id){

       super(id);

    }

}

publicclass CanonicalMapping {

    publicstaticvoid main(String[] args){

       int size=1000;

       Key[] keys=new Key[size];

       WeakHashMap<Key,Value> map=new WeakHashMap<Key,Value>();

       for(int i=0;i<size;i++){

           Key k=new Key(Integer.toString(i));

           Value v=new Value(Integer.toString(i));

           if(i%3==0)

              keys[i]=k;

           map.put(k, v);

       }

       System.gc();

    }

}

從列印結果可以看出,當執行System.gc()方法後,垃圾回收器只會回收那些僅僅持有弱引用的Key物件。id可以被3整除的Key物件持有強引用,因此不會被回收。

4.3用 WeakHashMap 堵住洩漏

在 SocketManager 中防止洩漏很容易,只要用 WeakHashMap 代替 HashMap 就行了。(這裡假定SocketManager不需要執行緒安全)。當對映的生命週期必須與鍵的生命週期聯絡在一起時,可以使用這種方法。用WeakHashMap修復 SocketManager。

publicclass SocketManager {

    private Map<Socket,User> m = new WeakHashMap<Socket,User>();

    publicvoid setUser(Socket s, User u) {

        m.put(s, u);

    }

    public User getUser(Socket s) {

        returnm.get(s);

    }

}

4.4配合使用引用佇列

WeakHashMap 用弱引用承載對映鍵,這使得應用程式不再使用鍵物件時它們可以被垃圾收集,get() 實現可以根據 WeakReference.get() 是否返回 null 來區分死的對映和活的對映。但是這只是防止 Map 的記憶體消耗在應用程式的生命週期中不斷增加所需要做的工作的一半,還需要做一些工作以便在鍵物件被收集後從 Map 中刪除死項。否則,Map 會充滿對應於死鍵的項。雖然這對於應用程式是不可見的,但是它仍然會造成應用程式耗盡記憶體。

引用佇列是垃圾收集器嚮應用程式返回關於物件生命週期的資訊的主要方法。弱引用有個建構函式取引用佇列作為引數。如果用關聯的引用佇列建立弱引用,在弱引用物件成為 GC 候選物件時,這個引用物件就在引用清除後加入到引用佇列中(具體參考上文軟引用示例)。

WeakHashMap 有一個名為 expungeStaleEntries() 的私有方法,大多數 Map 操作中會呼叫它,它去掉引用佇列中所有失效的引用,並刪除關聯的對映。

5.UML:使用關聯類指明特定形式的引用

關聯類能夠用來指明特定形式的引用,如弱(weak)、軟(soft)或虛 (phantom)引用。

也可以如下的構造型方式。