SoftReference, WeakReference, PhantomReference的區別

一段時間之前,我面試了一些Java高級工程師職位的候選人。在我問的衆多問題中的一個是「談談你對Java弱引用的理解」。我並無期待一個相似於技術論文同樣的答案。候選人若是回答說「嗯…,這不是和垃圾回收有關係嗎?」,我可能就已經很滿意了。然而出乎我意料的是我問的二十幾個候選人差很少都有五年以上的Java經驗,可是隻有兩我的知道弱引用的存在,只有一我的具備實際的使用經驗。我甚至嘗試解釋一下,以獲得「啊哦,是這樣啊」的迴應,可是並無。我不肯定爲何不少人對弱引用不夠了解,由於弱引用是從Java 1.2就引入並被普遍使用的功能。html

如今,我不會建議你爲了作一個不錯的Java開發人員而成爲一個弱引用的專家。可是我我的認爲你最起碼應該瞭解他們是什麼。不然你怎麼知道何時應該使用它們?這篇文章會對如下方面作一個扼要的說明:什麼是弱引用,怎麼使用它們,何時使用它們。java

強引用web

首先讓咱們回憶一下強引用。強引用是一個普通的引用,你天天都會使用。例如:面試

StringBuffer buffer = new StringBuffer();編程

建立一個新的StringBuffer()而且保存一個對它的強引用到buffer變量。是的,是的,這是小兒科,可是忍受我一下先。強引用重要的地方(使得它們成爲「強引用」的地方)就是它們是怎樣和垃圾回收器交互的。確切的說,若是一個對象能夠經過一個強引用鏈條被訪問到,它是不能被垃圾回收的。由於你不會想讓垃圾回收器銷燬你正在工做着的對象。這正是你想要的。緩存

當強引用太強了安全

咱們常常會遇到想擴展一些類的功能可是這些類又不太容易被擴展的情形。有時是類被標記爲了final,有時是類太複雜了,好比說一個工廠方法返回一個interface類型的對象,可是對象的具體實現是不知道的(甚至都不可能知道)。假設你必需要使用Widget類,可是因爲某種緣由你不可以繼承Widget去擴展更多的功能。編程語言

若是咱們須要跟蹤保存一些Widget額外的信息的話,應該怎麼辦?假設咱們須要跟蹤保存每個Widget的序列號(Serial Number),可是Widget並無序列號屬性。因爲Widget不能被繼承,咱們也不能添加序列號屬性。不要緊,咱們可使用HashMap:工具

serialNumberMap.put(widget, widgetSerialNumber);

乍一看,好像沒什麼問題。可是對widget的強引用確定會形成問題。咱們不得不清楚的知道何時某個Widget的序列號將再也不被須要,咱們能夠把它從HashMap中移除。不然將會產生內存泄漏(若是咱們應該從HashMap移除掉widget的時候卻沒有移除)或者使人費解的發現咱們丟掉了一些序列號(若是咱們移除掉了還在使用的widget)。若是這些問題很眼熟,它們實際上是非垃圾回收編程語言使用者在管理內存時面臨的問題。對於更加文明的編程語言好比Java,咱們是不該該關心這個問題的。網站

強引用另一個常見的問題是關於緩存的,特別是涉及到大的結構,好比圖片。假設你的應用程序須要處理用戶上傳的圖片,就像我作過的網站設計工具同樣。很天然地,你想要緩存這些圖片,由於從磁盤加載它們有很是大的開銷,而且你也想避免對一個可能很大的圖片同時加載兩份到內存。

由於一個圖片的緩存可使咱們避免從磁盤從新加載到內存(當咱們並非絕對的須要這樣),你可能很快會意識到緩存應當對內存中存在的每個圖片都有一個引用。然而使用常規的強引用,將會使得圖片一直保存在內存中。這將會使你(和上面同樣)不得不以某種方式決定圖片在何時再也不須要了,進而能夠從緩存中移除,以後GC能夠回收掉它。再一次的,你不得不模仿GC的行爲而且以人工的方式決定一個對象是否應該留在內存中。

弱引用(WeakReference

弱引用簡單的說是一個不足以使得一個對象保留在內存中的引用。弱引用容許你利用GC來斷定對象是否可達,而沒必要本身來斷定。建立弱引用的方式以下:

WeakReference weakWidget = new WeakReference(widget);

而後你在其餘地方可使用weakWidget.get() 來獲取實際的widget對象。固然弱引用不足以保證對象不被GC回收,因此你可能會突然發現(若是沒有強引用指向widget) weakWidget.get() 忽然開始返回null。

解決上面的「widget序列號」問題,最簡單的方式是使用內置的WeakHashMap類。除了鍵(注意:不是值!)使用WeakReference指向之外,WeakHashMap和HashMap工做方式徹底同樣。若是WeakHashMap的一個鍵變成了垃圾,對應的鍵值對將會自動從中移除。這避免了前面提到的問題,代碼也只是從使用HashMap簡單的改變爲使用WeakHashMap。若是你遵循慣用的用Map接口指向你的map類的風格,其餘的代碼甚至都不須要知道這種改變。

ReferenceQueue

當一個WeakReference開始返回null時,它所指向的對象變成了垃圾,這個WeakReference對象自己幾乎也沒什麼用了。這意味着有一些清除工做須要作。例如,WeakHashMap不得不移除掉這樣的死掉的鍵值對以免包含愈來愈多死掉的WeakReference。

ReferenceQueue使得跟蹤死掉的引用變得簡單。若是你給WeakReference的構造方法傳入一個ReferenceQueue,那麼當這個WeakReference的對象所指向的對象成爲垃圾的時候,這個WeakReference對象將會被插入這個ReferenceQueue。接下來,你能夠隔一段時間就處理一下ReferenceQueue,作一些對死掉的WeakReference任何你想作的清理工做。

不一樣程度的弱引用

到如今我只是提到了弱引用,可是實際上有四種從強到弱不一樣強度的引用:強引用,軟引用(SoftReference),弱引用(WeakReference)和虛幻引用(PhantomReference)。咱們已經討論了強引用和弱引用,接下來討論其餘兩種。

軟引用(SoftReference

SoftReference除了比WeakReference不急於扔掉所指向的對象之外它們是同樣的。一個對象若是最強的引用只是WeakReference,將會在下次GC發生的時候被遺棄掉。可是一個對象若是隻被軟引用指向,簡單的講它將會再堅持存在一段時間。

SoftReference並無被要求和WeakReference有什麼不一樣的行爲,可是被SoftReference引用的對象簡單的講只要內存足夠將會一直被保留。這是它們適合做爲緩存的根本所在,就像上面描述的圖像緩存同樣,由於你能夠交給GC去關心對象以哪一種方式可達(強引用對象將永遠不會從內存移除),GC有多麼的須要去回收緩存所佔用的內存。

虛幻引用(PhantomReference

PhantomReference與SoftReference和WeakReference都不同。它對於對象的引用很是的虛幻,以至於你甚至沒有辦法獲取被引用的對象—它的get()方法老是返回null。它惟一的用處就是跟蹤被引用的對象什麼時候進入到ReferenceQueue,從而知道被引用對象什麼時候死掉了。然而這和WeakReference有什麼不一樣呢?

確切的說,不一樣的地方在於什麼時候進入到ReferenceQueue。WeakReference在它所指的對象被GC認定爲只是弱引用可達的時候就會被放到ReferenceQueue中。這個發生在finalize()調用以前或者垃圾回收以前;理論上說所指向的對象甚至能夠經過finalize()方法「復活」,可是WeakReference將會保持死亡狀態。PhantomReference只有當對象從物理內存中移除的時候纔會進入到ReferenceQueue,而且因爲它的get () 方法老是返回null,這保證了你不可以「復活」一個瀕死的對象。

PhantomReference有什麼好處呢?我只知道兩個正式使用它的情形:首先它容許你準確的判斷一個對象何時從物理內存中移除。實際上這是惟一的判斷方式。大體上說這不是很是的有用,可是在某些特定的場合可能會很方便:若是你確切的知道一個圖片應當會被回收掉,你能夠等到它確實已經從內存中移除了而後再加載下一個圖片,由此你能夠下降嚇人的OutOfMemoryError發生的機率。

其次,PhantomReference避免了對象終結(finalization)固有的問題:finalize()方法能夠經過建立強引用來「復活」對象。這又能怎麼樣?問題是一個複寫了finalize()方法的對象,如今必須須要至少通過兩輪的GC過程,才能被回收掉。第一輪斷定一個對象爲垃圾,從而能夠去調用它的finalize()方法。因爲在finalize()方法中「復活」的可能性(很小但確實存在),GC不得不在真正回收對象以前再跑一輪。而且由於finalize()方法的調用並非即時性的,當對象在等待它的finalize()方法被調用的時候,GC可能已經跑過好多輪了。這意味着從斷定對象爲垃圾到真正回收內存之間存在着嚴重的延時,這也是你爲何會在大部分對象爲垃圾的狀況下還會遇到OutOfMemoryError的緣由。

使用PhantomReference,這種狀況是不可能的—當一個PhantomReference入隊列的時候,你絕對沒有辦法獲取已經死掉的對象(這很好,由於它已經不在內存中了)。由於PhantomReference不能被用來複活一個對象, GC若是發現對象只是被虛幻引用,在第一輪的時候就能夠當即回收這個對象。而你能夠在任何方便的時候清理資源。

或許finalize()方法原本從最初就不該該被提供出來。PhantomReference使用起來絕對是更安全和更高效的,而且移除掉finalize()方法,會使得JVM的某些部分變得至關的簡單。可是PhantomReference的使用會包含比較多的工做,因此我也認可我大部分時間也仍是在使用finalize()。好消息是至少你有一個選擇。

總結

我肯定大家中的某些人在喃喃自語,由於我在討論一個存在了好多年的功能,而並非在討論一個從未被討論過的話題。以個人經驗,不少Java開發者確實對弱引用不是太瞭解(若是知道一點的話)。我感受須要寫篇文章刷新一下記憶,但願你能從這篇回顧中有一點兒收穫。

 

做者公衆號(碼年)掃碼關注:

 

英文原文:

https://web.archive.org/web/20061130103858/http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html

相關文章
相關標籤/搜索