四類引用的原理及使用

Java 2 平臺引入了 java.lang.ref 包,其中包括的類可以讓您引用對象,而不將它們留在內存中。這些類還提供了與垃圾收集器(garbage collector)之間有限的交互。Peter Haggar 在本文中分析了 SoftReference、WeakReference 和 PhantomReference 類的功能和行爲,並就這些類的使用給出了一些編程風格上的建議。
當在 Java 2 平臺中首次引入 java.lang.ref 包(其中包含 SoftReference、WeakReference 和 PhantomReference 類)時,它的實用性顯然被過分誇大了。它包含的類可能是有用的,但這些類具有的某些侷限性會使它們顯得不是很有吸引力,而且其應用程序也將特別侷限於解決一類特定的問題。

垃圾收集概述
引用類的主要功能就是能夠引用仍可以被垃圾收集器回收的對象。在引入引用類之前,我們只能使用強引用(strong reference)。舉例來說,下面一行代碼顯示的就是強引用 obj:


Object obj = new Object();


obj 這個引用將引用堆中存儲的一個對象。只要 obj 引用還存在,垃圾收集器就永遠不會釋放用來容納該對象的存儲空間。

當 obj 超出範圍或被顯式地指定爲 null 時,垃圾收集器就認爲沒有對這個對象的其它引用,也就可以收集它了。然而您還需要注意一個重要的細節:僅憑對象可以被收集並不意味着垃圾收集器的一次指定運行就能夠回收它。由於各種垃圾收集算法有所不同,某些算法會更頻繁地分析生存期較短的對象,而不是較老、生存期較長的對象。因此,一個可供收集的對象可能永遠也不會被回收。如果程序在垃圾收集器釋放對象之前結束,這種情況就可能會出現。因此,概括地說,您永遠無法保證可供收集的對象總是會被垃圾收集器收集。

這些信息對於您分析引用類是很重要的。由於垃圾收集有着特定的性質,所以引用類實際上可能沒有您原來想像的那麼有用,儘管如此,它們對於特定問題來說還是很有用的類。軟引用(soft reference)、弱引用(weak reference)和虛引用(phantom reference)對象提供了三種不同的方式來在不妨礙收集的情況下引用堆對象。每種引用對象都有不同的行爲,而且它們與垃圾收集器之間的交互也有所不同。此外,這幾個新的引用類都表現出比典型的強引用“更弱”的引用形式。而且,內存中的一個對象可以被多個引用(可以是強引用、軟引用、弱引用或虛引用)引用。在進一步往下討論之前,讓我們來看看一些術語:

強可及對象(strongly reachable):可以通過強引用訪問的對象。


軟可及對象(softly reachable):不是強可及對象,並且能夠通過軟引用訪問的對象。


弱可及對象(weakly reachable):不是強可及對象也不是軟可及對象,並且能夠通過弱引用訪問的對象。


虛可及對象(phantomly reachable):不是強可及對象、軟可及對象,也不是弱可及對象,已經結束的,可以通過虛引用訪問的對象。


清除:將引用對象的 referent 域設置爲 null,並將引用類在堆中引用的對象聲明爲可結束的。
SoftReference 類
SoftReference 類的一個典型用途就是用於內存敏感的高速緩存。SoftReference 的原理是:在保持對對象的引用時保證在 JVM 報告內存不足情況之前將清除所有的軟引用。關鍵之處在於,垃圾收集器在運行時可能會(也可能不會)釋放軟可及對象。對象是否被釋放取決於垃圾收集器的算法以及垃圾收集器運行時可用的內存數量。

WeakReference 類
WeakReference 類的一個典型用途就是規範化映射(canonicalized mapping)。另外,對於那些生存期相對較長而且重新創建的開銷也不高的對象來說,弱引用也比較有用。關鍵之處在於,垃圾收集器運行時如果碰到了弱可及對象,將釋放 WeakReference 引用的對象。然而,請注意,垃圾收集器可能要運行多次才能找到並釋放弱可及對象。

PhantomReference 類
PhantomReference 類只能用於跟蹤對被引用對象即將進行的收集。同樣,它還能用於執行 pre-mortem 清除操作。PhantomReference 必須與 ReferenceQueue 類一起使用。需要 ReferenceQueue 是因爲它能夠充當通知機制。當垃圾收集器確定了某個對象是虛可及對象時,PhantomReference 對象就被放在它的 ReferenceQueue 上。將 PhantomReference 對象放在 ReferenceQueue 上也就是一個通知,表明 PhantomReference 對象引用的對象已經結束,可供收集了。這使您能夠剛好在對象佔用的內存被回收之前採取行動。

垃圾收集器和引用交互
垃圾收集器每次運行時都可以隨意地釋放不再是強可及的對象佔用的內存。如果垃圾收集器發現了軟可及對象,就會出現下列情況:

SoftReference 對象的 referent 域被設置爲 null,從而使該對象不再引用 heap 對象。


SoftReference 引用過的 heap 對象被聲明爲 finalizable。


當 heap 對象的 finalize() 方法被運行而且該對象佔用的內存被釋放,SoftReference 對象就被添加到它的 ReferenceQueue(如果後者存在的話)。
如果垃圾收集器發現了弱可及對象,就會出現下列情況:

WeakReference 對象的 referent 域被設置爲 null,從而使該對象不再引用 heap 對象。


WeakReference 引用過的 heap 對象被聲明爲 finalizable。


當 heap 對象的 finalize() 方法被運行而且該對象佔用的內存被釋放時,WeakReference 對象就被添加到它的 ReferenceQueue(如果後者存在的話)。
如果垃圾收集器發現了虛可及對象,就會出現下列情況:

PhantomReference 引用過的 heap 對象被聲明爲 finalizable。


與軟引用和弱引用有所不同,PhantomReference 在堆對象被釋放之前就被添加到它的 ReferenceQueue。(請記住,所有的 PhantomReference 對象都必須用經過關聯的 ReferenceQueue 來創建。)這使您能夠在堆對象被回收之前採取行動。
請考慮清單 1 中的代碼。

清單 1. 使用 WeakReference 及 ReferenceQueue 的示例代碼
//Create a strong reference to an object
MyObject obj = new MyObject(); //1

//Create a reference queue
ReferenceQueue rq = new ReferenceQueue(); //2

//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq); //3


行 //1 創建 MyObject 對象,而行 //2 則創建 ReferenceQueue 對象。行 //3 創建引用其引用對象 MyObject 的 WeakReference 對象,還創建它的 ReferenceQueue。請注意,每個對象引用(obj、rq 及 wr)都是強引用。要利用這些引用類,您必須取消對 MyObject 對象的強引用,方法是將 obj 設置爲 null。前面說過,如果不這樣做,對象 MyObject 永遠都不會被回收,引用類的任何優點都會被削弱。

每個引用類都有一個 get() 方法,而 ReferenceQueue 類有一個 poll() 方法。get() 方法返回對被引用對象的引用。在 PhantomReference 上調用 get() 總是會返回 null。這是因爲 PhantomReference 只用於跟蹤收集。poll() 方法返回已被添加到隊列中的引用對象,如果隊列中沒有任何對象,它就返回 null。因此,執行清單 1 之後再調用 get() 和 poll() 的結果可能是:


wr.get(); //returns reference to MyObject
rq.poll(); //returns null


現在我們假定垃圾收集器開始運行。由於 MyObject 對象沒有被釋放,所以 get() 和 poll() 方法將返回同樣的值;obj 仍然保持對該對象進行強引用。實際上,對象佈局還是沒有改變,和圖 1 所示的差不多。然而,請考慮下面的代碼:


obj = null;
System.gc(); //run the collector


現在,調用 get() 和 poll() 將產生與前面不同的結果:


wr.get(); //returns null
rq.poll(); //returns a reference to the WeakReference object


這種情況表明,MyObject 對象(對它的引用原來是由 WeakReference 對象進行的)不再可用。這意味着垃圾收集器釋放了 MyObject 佔用的內存,從而使 WeakReference 對象可以被放在它的 ReferenceQueue 上。這樣,您就可以知道當 WeakReference 或 SoftReference 類的 get() 方法返回 null 時,就有一個對象被聲明爲 finalizable,而且可能(不過不一定)被收集。只有當 heap 對象完全結束而且其內存被回收後,WeakReference 或 SoftReference 纔會被放到與其關聯的 ReferenceQueue 上。清單 2 顯示了一個完整的可運行程序,它展示了這些原理中的一部分。這段代碼本身就頗具說明性,它含有很多註釋和打印語句,可以幫助您理解。

清單 2. 展示引用類原理的完整程序
import java.lang.ref.*;
class MyObject
{
protected void finalize() throws Throwable
{
System.out.println("In finalize method for this object: " +
this);
}
}

class ReferenceUsage
{
public static void main(String args[])
{
hold();
release();
}

public static void hold()
{
System.out.println("Example of incorrectly holding a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);

//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();

//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);

System.out.println("The weak reference is " + wr);

//Check to see if it´s on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());

System.out.println("Calling GC");
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}

public static void release()
{
System.out.println("");
System.out.println("Example of correctly releasing a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);

//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();

//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);

System.out.println("The weak reference is " + wr);

//Check to see if it´s on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());

System.out.println("Set the obj reference to null and call GC");
obj = null;
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
}


用途和風格
這些類背後的原理就是避免在應用程序執行期間將對象留在內存中。相反,您以軟引用、弱引用或虛引用的方式引用對象,這樣垃圾收集器就能夠隨意地釋放對象。當您希望儘可能減小應用程序在其生命週期中使用的堆內存大小時,這種用途就很有好處。您必須記住,要使用這些類,您就不能保留對對象的強引用。如果您這麼做了,那就會浪費這些類所提供的任何好處。

另外,您必須使用正確的編程風格以檢查收集器在使用對象之前是否已經回收了它,如果已經回收了,您首先必須重新創建該對象。這個過程可以用不同的編程風格來完成。選擇錯誤的風格會導致出問題。請考慮清單 3 中從 WeakReference 檢索被引用對象的代碼風格:

清單 3. 檢索被引用對象的風格
obj = wr.get();
if (obj == null)
{
wr = new WeakReference(recreateIt()); //1
obj = wr.get(); //2
}
//code that works with obj


研究了這段代碼之後,請看看清單 4 中從 WeakReference 檢索被引用對象的另一種代碼風格:

清單 4. 檢索被引用對象的另一種風格
obj = wr.get();
if (obj == null)
{
obj = recreateIt(); //1
wr = new WeakReference(obj); //2
}
//code that works with obj


請比較這兩種風格,看看您能否確定哪種風格一定可行,哪一種不一定可行。清單 3 中體現出的風格不一定在所有情況下都可行,但清單 4 的風格就可以。清單 3 中的風格不夠好的原因在於,if 塊的主體結束之後 obj 不一定是非空值。請考慮一下,如果垃圾收集器在清單 3 的行 //1 之後但在行 //2 執行之前運行會怎樣。recreateIt() 方法將重新創建該對象,但它會被 WeakReference 引用,而不是強引用。因此,如果收集器在行 //2 在重新創建的對象上施加一個強引用之前運行,對象就會丟失,wr.get() 則返回 null。

清單 4 不會出現這種問題,因爲行 //1 重新創建了對象併爲其指定了一個強引用。因此,如果垃圾收集器在該行之後(但在行 //2 之前)運行,該對象就不會被回收。然後,行 //2 將創建對 obj 的 WeakReference。在使用這個 if 塊之後的 obj 之後,您應該將 obj 設置爲 null,從而讓垃圾收集器能夠回收這個對象以充分利用弱引用。清單 5 顯示了一個完整的程序,它將展示剛纔我們描述的風格之間的差異。(要運行該程序,其運行目錄中必須有一個“temp.fil”文件。

清單 5. 展示正確的和不正確的編程風格的完整程序。
import java.io.*;
import java.lang.ref.*;

class ReferenceIdiom
{
public static void main(String args[]) throws FileNotFoundException
{
broken();
correct();
}

public static FileReader recreateIt() throws FileNotFoundException
{
return new FileReader("temp.fil");
}

public static void broken() throws FileNotFoundException
{
System.out.println("Executing method broken");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);

System.out.println("wr refers to object " + wr.get());

System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj.
obj = null;
System.gc();

System.out.println("wr refers to object " + wr.get());

//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
wr = new WeakReference(recreateIt());
System.gc(); //FileReader object is NOT pinned...there is no
//strong reference to it. Therefore, the next
//line can return null.
obj = (FileReader)wr.get();
}
System.out.println("wr refers to object " + wr.get());
}

public static void correct() throws FileNotFoundException
{
System.out.println("");
System.out.println("Executing method correct");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);

System.out.println("wr refers to object " + wr.get());

System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj
obj = null;
System.gc();

System.out.println("wr refers to object " + wr.get());

//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
obj = recreateIt();
System.gc(); //FileReader is pinned, this will not affect
//anything.
wr = new WeakReference(obj);
}
System.out.println("wr refers to object " + wr.get());
}
}


總結
如果使用得當,引用類還是很有用的。然而,由於它們所依賴的垃圾收集器行爲有時候無法預知,所以其實用性就會受到影響。能否有效地使用它們還取決於是否應用了正確的編程風格;關鍵在於您要理解這些類是如何實現的以及如何對它們進行編程。
=================================================================================

Java 對象的狀態有:

* 已創建(created)
* 強可達(strong reachable)
* 不可見(invisible)
* 不可達(unreachable)
* 已收集(collected)
* 終化(finalized)
* 已回收(deallocated)

Java對象生命週期的狀態轉換: {image:img=objectstatus.jpg|width=400} 引用對象
三種新的引用類型:

* 軟引用(soft reference)
* 弱引用(weak reference)
* 幻引用(phantom reference)

強可達(Strong Reachable)
定義: ~An object is strong reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strong reachable by the thread that created it.~
處於強可達狀態的對象, 在任何情況下都不會被回收掉. 軟可達(Softly Reachable)
定義:~An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.~
含義是:當對象不處於強可達狀態, 並且可以通過軟引用進行訪問時, 即處於軟可達狀態.
當程序申請內存的時候, 垃圾收集器會判斷是否開始回收處於軟可達狀態的對象, 如果決定回收某個對象, 那麼垃圾收集器會清除所有指向該對象的軟引用, 如果任何處於其它軟可達狀態的對象可以通過強引用訪問該對象, 那麼指向這些對象的軟引用也會被清除掉. 垃圾收集器在決定哪些軟可達狀態的對象被收集時, 採用"最久未被使用"原則, 或稱"最不常使用"原則. 垃圾收集器也保證在OutOfMemeryError產生以前, 所有的軟引用都被清除.

* 產生和使用一個軟引用

// createSoftReference sr = new SoftReference(new SomeObject());// getSomeObject o = (SomeObject) sf.get();// create in a reference queue;ReferenceQueue queue = new ReferenceQueue();SoftReference sr = new SoftReference(new SomeObject(), queue);

弱可達(Weakly Reachable)
定義:~An Object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference.~
垃圾收集器會一次清除所有弱引用. 幻可達(Phantomly Reachable)
定義:~An object is phantomly reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.~
幻引用不能直接創建. 必須通過向引用隊列等級的途徑來創建:

ReferenceQueue queue = new ReferenceQueue();PhantomReference pr = new PhantomReference (new SomeObject(), queue);

你不可能從幻引用再次得到對象, pr.get()永遠返回null. 另外, 必須調用Reference.clear()手工清除幻引用. All About ReferenceObjects No InterWiki reference defined in properties for Wiki called '[http'!)]
Reference Objects No InterWiki reference defined in properties for Wiki called '[http'!)]
Reference Objects and Garbage Collection No InterWiki reference defined in properties for Wiki called '[http'!)]
\[Jike Thread\?Soft, Weak, and Phantom References|http://www-124.ibm.com/pipermail/jikesrvm-core/2003-May/000365.html]


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1492810
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;


public class Testone {
public static void main(String args[]){
A a=new A();
//a.test();
//SoftReference sr = new SoftReference(a);
ReferenceQueue<A> rq = new ReferenceQueue<A>();
WeakReference<A> wr = new WeakReference<A>(a, rq);
a = null;
System.out.println(wr.get());
System.out.println(rq.poll());
System.gc();
System.runFinalization();
System.out.println(wr.get());
System.out.println(rq.poll());
if (wr != null) {
a = (A)wr.get();
System.out.println("asdasdas");
a.test();
}
else{
a = new A();
System.out.println("123123");
a.test();
a = null;
wr = new WeakReference<A>(a);
}

}
}
class A{
void test(){
System.out.println("A.test()");
}
}
一個ReferenceQueue的例子(軟引用)
查看結果,你就會清楚
如果我們把a = null;注釋掉,那麼軟引用就沒有發揮出作用


import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;


public class Testone {
public static void main(String args[]){
A a=new A();
//a.test();
//SoftReference sr = new SoftReference(a);
SoftReference<A> sr = new SoftReference<A>(a);
a = null;
System.out.println(sr.get());
ArrayList<Object> o=new ArrayList<Object>(15462000);
//System.gc();
//System.runFinalization();
System.out.println(sr.get());
if (sr != null) {
a = (A)sr.get();
System.out.println("asdasdas");
a.test();
}
else{
a = new A();
System.out.println("123123");
a.test();
a = null;
sr = new SoftReference<A>(a);
}

}
}
class A{
void test(){
System.out.println("A.test()");
}
}


運行結果:

A@35ce36
null
asdasdas
Exception in thread "main" java.lang.NullPointerException
at Testone.main(Testone.java:22)


本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/leboy/archive/2009/09/06/4524202.aspx
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章