之前我招聘過 高級java工程師,其中一個面試題目是「你對weak reference瞭解多少?」。這個話題比較偏,不期望每一個人都能清楚它的細節。若是面試的人說「Umm...好像和gc(垃圾回收)有點關係?」,那 我就至關滿意了。實際狀況倒是20多個5年java開發經驗的工程師只有2個知道有weak reference這麼回事,其中1個是真正清楚的。我試圖給他們一些提示,指望有人會恍然大悟,惋惜沒有。不知道爲何這個特性uncommon,確切 地說,是至關uncommon,要知道這是在java1.2中推出的,那是7年前的事了。java
不必成爲weak reference專家,裝成資深java工程師(就像茴香豆的茴字有四種寫法)。可是至少要了解一點點,知道是怎麼回事。下面告訴你什麼是weak references,怎麼用及什麼時候用它們。面試
l Strong references
從強引用(Strong references)開始。你天天用的就是strong reference,好比下面的代碼:StringBuffer buffer = new StringBuffer()
;
建立了一個StringBuffer對象,變量buffer保存對它的引用。這過小兒科了!是的,請保持點耐心。Strong reference,是什麼使它們‘strong’?——是gc處理它們的方式:若是一個對象經過一串強引用鏈可達,那麼它們不會被垃圾回收。你總不會喜歡gc把你正在用的對象回收掉吧。緩存
l When strong references are too strong
咱們有時候用到一些不能修改也不能擴展的類,好比final class,再好比,經過Factory建立的對象,只有接口,連是什麼實現都不知道。想象一下,你正在用widget類,須要知道每一個實例的擴展信息,好比它是第幾個被建立的widget實例(即序列號),假設條件不容許在類中添加方法,widget類本身也沒有這樣的序列號,你準備怎麼辦?用HashMap!serialNumberMap.put(widget, widgetSerialNumber)
,用變量記錄新實例的序列號,建立實例時把實例和它的序列號放到HashMap中。很顯然,這個Map會不斷變大,從而形成內存泄漏。你要說,沒關係,在不用某個實例時就從map中刪除它。是的,這可行,可是「put——remove」,你不以爲你在作與內存管理「new——delete」相似的事嗎?像全部本身管理內存的語言同樣,你不能有遺漏。這不是java風格。
另 一個很廣泛的問題是緩存,特別是很耗內存的那種,好比圖片緩存。想象一下,有個項目要管理用戶本身提供的圖片,好比像我正在作的網站編輯器。天然地你會把 這些圖片緩存起來,由於每次從磁盤讀取會很耗時,並且能夠避免在內存中一張圖片出現多份。你應該可以很快地意識到這有內存危機:因爲圖片佔用的內存無法被 回收,內存早晚要用完。把一部分圖片從緩存中刪除放到磁盤上去!——這涉及到何時刪除、哪些圖片要刪除的問題。和widget類同樣,不是嗎,你在作 內存管理的工做。安全
l Weak reference
Weak reference,簡單地說就是這個引用不會強到迫使對象必須保持在內存中。Gc不會碰Strong reference可達的對象,但能夠碰weak reference可達的對象。下面建立一個weak reference:WeakReference weakWidget = new WeakReference(widget)
,使用weakWidget.get()
來取到widget對象。注意,get()可能返回null。什麼?null?何時變成null了?——當內存不足垃圾回收器把widget回收了時(若是是Strong reference,這是不可能發生的)。你會問,變成null以後要想再獲得widget怎麼辦?答案是沒有辦法,你得從新建立widget對象,對cache系統這很容易作到,好比圖片緩存,從磁盤載入圖片便可(內存中的每份圖片要在磁盤上保存一份)。
像 上面的「widget序列號」問題,最簡單的是用jdk內含的WeakHashMap類。WeakHashMap和HashMap的工做方式相似,不過它 的keys(注意不是values)都是weak reference。若是WeakHashMap中的一個key被垃圾回收了,那麼這個entry會被自動刪除。若是使用的是Map接口,那麼實例化時只 需把HashMap改爲WeakHashMap,其它代碼都不用變,就這麼簡單。編輯器
l Reference queque
一旦WeakReference.get()
返回null,它指向的對象被垃圾回收,WeakReference對象就一點用都沒有了,若是要對這些沒有的WeakReference作些清理工做怎麼辦?好比在WeakHashMap中要把回收過的key從Map中刪除掉。jdk中的ReferenceQueue類使你能夠很容易地跟蹤dead references。WeakReference類的構造函數有一個ReferenceQueue參數,當指向的對象被垃圾回收時,會把WeakReference對象放到ReferenceQueue中。這樣,遍歷ReferenceQueue能夠獲得全部回收過的WeakReference。WeakHashMap的作法是在每次調用size()、get()等操做時都先遍歷ReferenceQueue,處理那些回收過的key,見jdk的源碼WeakHashMap# expungeStaleEntries()。函數
l Different degrees of weakness
上面咱們僅僅提到「weak reference」,實際上根據弱的層度不一樣有四種引用:強(strong)、軟(soft)、弱(weak)、虛(phantom)。咱們已經討論過strong和weak,下面看下soft和phantom。網站
n Soft reference
Soft reference和weak reference的區別是:一旦gc發現對象是weak reference可達就會把它放到ReferenceQueue中,而後等下次gc時回收它;當對象是Soft reference可達時,gc可能會向操做系統申請更多內存,而不是直接回收它,當實在沒轍了纔回收它。像cache系統,最適合用Soft reference。spa
n Phantom reference
虛引用Phantom reference與Soft reference和WeakReference的使用有很大的不一樣:它的get()方法老是返回null(不信能夠看jdk的PhantomReference源碼)。這意味着你只能用PhantomReference自己,而得不到它指向的對象。它的惟一用處是你可以在ReferenceQueue中知道它被回收了。爲什麼要有這種「不一樣」?
何 時進入ReferenceQueue產生了這種「不一樣」。WeakReference是在它指向的對象變得弱可達(weakly reachable)時當即被放到ReferenceQueue中,這在finalization、garbage collection以前發生。理論上,你能夠在finalize()方法中使對象「復活」(使一個強引用指向它就好了,gc不會回收它),但 WeakReference已經死了(死了?不太明白做者的確切意思。在finalize中復活對象不太可以說明問題。理論上你能夠復活 ReferenceQueue中的WeakReference指向的對象,但無法復活PhantomReference指向的對象,我想這纔是它們的「不 同」)。而PhantomReference不一樣,它是在garbage collection以後被放到ReferenceQueue中的,無法復活。
PhantomReferences的 價值在哪裏?我只說兩點:一、你能知道一個對象已經從內存中刪除掉了,事實上,這是惟一的途徑。這可能不是頗有用,只能用在某些特別的場景中,好比維護巨 大的圖片:只有圖片對象被回收以後纔有必要再載入,這在很大程度上能夠避免OutOfMemoryError。二、能夠避免finalize()方法的缺 點。在finalize方法中能夠經過新建強引用來使對象復活。你可能要說,那又怎麼樣?——finalize的問題是對那些重載了finalize方法 的對象垃圾回收器必須判斷兩遍才能決定回收它。第一遍,判斷對象是否可達,若是不可達,看是否有finalization,若是有則調用,不然回收;第二 遍判斷對象是否可達,若是不可達,則回收。因爲finalize是在內存回收以前調用的,那麼在finalize中可能出現 OutOfMemoryError,即便不少對象能夠被回收。用PhantomReference就不會出現這種狀況,當 PhantomReference進入ReferenceQueue以後就無法再得到所指向的對象(它已經從內存中刪除了)。因爲 PhantomReference不能使對象復活,因此它指向的對象能夠在第一遍時回收,有finalize方法的對象就不行。能夠證 明,finalize方法不是首選。PhantomReference更安全更有效,能夠簡化VM的工做。雖然好處多,但要寫的代碼也多。因此我坦白承 認,大部分狀況我仍是用finalize。無論怎麼樣,你多了個選擇,不用在finalize這棵樹上吊死。操作系統
l 總結
我打賭有人在嘟囔,說我在講老黃曆,沒什麼鮮貨。你說得沒錯,不過,以個人經驗仍有不少java工程師對weak reference沒甚瞭解,這樣一堂入門課對他們頗有必要。真心但願你能從這篇文章中獲得一點收穫。code