當對象改變其可達性狀態時,對該對象的引用就可能會被置於引用隊列(reference queue)中。這些隊列被記憶體回收行程用來與我們的代碼溝通有關對象可達性變化的情況。這些隊列是探測可達性變化的最佳方式,儘管我們也可以通過檢查get方法的傳回值是不是null來探測對象的可達性變化。
引用對象在構造時可以與特定隊列建立關聯。Reference的每一個子類都提供了如下形式的構造器:
.public Strength Reference (T referent, ReferenceQueueq):該方法用給定的指稱對象建立新的引用對象,並且把該引用對象註冊到給定的隊列中。弱引用和軟引用在記憶體回收行程確定它們的指稱對象進人它們所表示的特定可達性狀態之後,就會被插人到隊列中,並且這兩種引用在插人隊列前都會被清除。虛引用也會在記憶體回收行程確定它的指稱對象進入虛可達狀態之後,被插入到隊列中,但是它們不會被清除。一旦引用對象被記憶體回收行程插人到隊列中,其get方法的傳回值就肯定會是null,因此該對象就再也不能複活了。
將引用對象註冊到引用隊列中並不會建立隊列和引用對象之間的引用。如果我們的引用對象本身變成了不可達的,那麼它就不能插人隊列了。因此我們的應用需要保持對所有引用對象的強引用。
ReferenceQueue類提供了三個用來移除隊列中引用的方法:
- .public Reference < ? extends下>poll ():用於移除並返回該隊列中的下一個引用對象,如果隊列為空白,則返回null.
- .public Referenceremove ()throws InterruptedException:用於移除並返回該隊列中的下一個引用對象,該方法會在隊列返回可用引用對象之前一直阻塞。
- .public Referenceremove (long timeout) throws interrupte-dException:用於移除並返回隊列中的下一個引用對象。該方法會在隊列返回可用引用對象之前一直阻塞,或者在超出指定逾時後結束。如果超出指定逾時,則返回null.如果指定逾時為0,意味著將無限期地等待。
poll方法使得線程可以查詢某個引用是否在隊列中,並且在該引用存在於隊列中時執行特定的動作。remove方法可以處理更複雜(更少見)的情況,在該方法中有一個專門的線程負責從隊列中移除引用,並執行恰當的動作。這些方法的阻塞行為和object.wait中定義的阻塞行為相同。對於特定的引用,我們可以通過其isEnqueued方法來查詢它是否在隊列中,也可以通過調用其enqueue方法將其強制插入到隊列中,但是通常這種插人是由記憶體回收行程完成的。
引用隊列中的虛引用可以用來確定對象何時可以被回收。我們不可能通過虛引用來訪問任何對象,即使該對象通過其他方式是可達的也是如此,因為虛引用的get方法總是返回null,事實上,用虛引用來尋找要回收的對象是最安全的方法,因為弱引用和軟引用在對象可終結之後會被插人到隊列中,而虛引用是在指稱對象被終結之後插人到隊列中的,即在該對象可以執行某些操作的最後時刻之後插人隊列的,所以它是絕對安全的。如果可以的話,應該總是使用虛引用,因為其他引用會存在finalize方法使用可終結對象的可能性。
考慮一個資源管理員的例子,它可以控制外部資源集合的訪問。對象可以請求訪問某項外部資源,並且直至操作完成才結束訪問,在此之後,它們應該將所用資源返回給資源管理員。如果這項資源是共用的,它的使用權就會在多個對象之間傳遞,甚至可能會在多個線程之間傳遞,因此我們很難確定哪個對象是這項資源最後的使用者,從而也就很難確定哪段代碼將負責返回這項資源。為了處理這種情況,資源管理員可以通過將資源關聯到被稱為鍵( key)的特殊對象上,實現這項資源的自動回收。只要鍵對象是可達的,我們就認為這項資源還在使用中;只要鍵對象可以被當作記憶體回收,這項資源就會被自動釋放。下面的代碼是對上述資源的抽象表示:
interface Resource{ void use(Object key, Object…args); void release(); }
當獲得某項資源時,其鍵對象必須提供給資源管理員。對於交還的Resource執行個體,只有在它得到了其對應的鍵時,才可以使用這項資源。這樣就可以確保在鍵被回收之後,它所對應的資源就不能再被使用了,即便表示這項資源的Resource對象本身可能仍然是可達的。請注意,Resource對象並未儲存對鍵對象的強引用,這一點很重要,因為這可以防止鍵對象變為不可達的,從而造成資源不能收回。Resource的實現可以嵌套在資源管理員中:
private static class ResourceImpl implements Resource{ int keyHash; boolean needsRelease=false ResourceImpl(Object key){ keyHash=System.identityHashCode(key); //=set up the external resource needsRelease=true; } public void use(Object key,Object... args){ if (System.identityHashCode(key)!=keyHash) throw new IlleqalArgumentException("wrong key" //...use the resource } public synchronized void release(){ if (needsRelease){ needsRelease=false: //=release the resource } } }
在資源被建立時就儲存了鍵對象的散列碼,並且無論何時調用use方法,它都會檢查是否提供了相同的鍵。對資源的實際使用可能還需要進行同步,但是為了簡單起見,我們在這裡把它們省略了。release方法負責釋放資源,它可以由資源的使用者在使用結束之後直接調用,也可 以由資源管理員在鍵對象不再被引用時調用。因為我們將使用獨立的線程來監視引用隊列,所以release方法必須是synchronized的,並且必須允許多次調用。
實際的資源管理員具有如下形式:
public final class ResourceManager{ final ReferenceQueue
鍵對象可以是任意對象,與讓資源管理員分配鍵對象相比,這賦予了資源使用者極大的靈活性。在調用getResource方法時,會建立一個新的Resource工mpl對象,同時會把提供給該方法的鍵傳遞給這個新的ResourceImpl對象。然後會建立一個虛引用,其指稱對象就是傳遞給該方法的鍵,之後這個虛引用會被插人到資源管理員的引用隊列中。最後所建立的虛引用和引用對象會被儲存到映射表中,這個映射表有兩個用途:一是保持所有的虛引用對象都是可達的,二是可以提供便捷的方法來查詢每個虛引用所關聯的實際的Resource對象。(另一種方式是子類化PhantomReference並將Resource對象存在一個欄位中。)
如果鍵對象變成了不可達的,那麼資源管理員會使用獨立的“收割機”(reaper)線程來處理資源。shutdown方法通過終止收割機線程(以響應中斷)從而導致getResource方法拋出Ille-llleStateException異常來“關閉”資源管理員。在這個簡單的設計中,任何在資源管理員關閉之後插人隊列的引用都不會得到處理。實際的收割機線程如下:
class ReaperThread extends Thread{ public void run(){ //run until interrupted while (true){ try{ Reference ref=queue.remove(); Resource res=null; synchronized(ResourceManager.this){ res=refs.get(ref); refs . remove(ref); } res .release(); ref.clear(); } catch (InterruptedException ex){ break;//all done } } } }
ReaperThread是內部類,並且給定的收割機線程會一直運行,直至與其相關聯的資源管理員關閉。該線程會在remove方法上阻塞,直至與特定鍵相關聯的虛引用被插人到引用隊列中為止。這個虛引用可以從映射表中擷取對Resource對象的引用,然後這一項“鍵一引用”對將會從映射表中移除。緊接著,在Resource對象上調用其release方法來釋放這項資源。最後,
虛引用被清除,使得鍵可以被回收。
作為一種替代使用獨立線程的方案,凡是在引用隊列上調用poll方法並釋放其鍵已經變為不可達的所有資源的操作都可以用getResourc“方法來替代,shutdow”方法也可以用來執行最後的poll操作。而資源管理員的語義將依賴於實際的資源類型和資源使用的模式。
使用引用隊列的設計與直接使用終結(特別是使用虛引用)的設計相比,要可靠得多。但是我們要記住,對於引用對象插入到引用隊列中的確切時間和位置都是不能確定的,我們也不能確定在應用程式終止的時候,所有可插人的引用是否都匕經插人到了引用隊列中。如果我們需要確保所有資源在應用程式終止之前都能夠被釋放掉,就必須要安裝必要的關閉掛鈎或者使用由應用程式定義的其他協議來確保實現這一點。