關於強引用、軟引用、弱引用、幻象引用的區別,在BAT這樣大公司的面試題中也經常出現,可能有些小伙伴覺得這個知識點比較冷門,但其實大家在開發中經常用到,如new一個對象的時候就是強引用的應用。
在java語言中,除了原始數據類型(boolean、byte、short、char、int、float、double、long)的變量,其他所有都是所謂的引用類型,指向各種不同的對象。理解這些引用的區別,對於掌握java對象生命周期和JVM內部相關機制非常有幫助。也有助於更深刻的理解底層對象生命周期、垃圾收集機制等,對設計可靠的緩存框架、診斷應用OOM等問題也大有裨益。
這四種應用主要的區別體現在對象不同的可達性狀態和對垃圾收集的影響,他們之間的可達性狀態可以參看下圖:
1.強引用(strong reference)
強引用就是我們最常見的普通對象引用(如new 一個對象),只要還有強引用指向一個對象,就表明此對象還“活着”。在強引用面前,即使JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),讓程序異常終止,也不會靠回收強引用對象來解決內存不足的問題。對於一個普通的對象,如果沒有其他的引用關系,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為null,就意味着此對象可以被垃圾收集了。但要注意的是,並不是賦值為null后就立馬被垃圾回收,具體的回收時機還是要看垃圾收集策略的。
如Object obj = new Object();
2.軟引用(soft reference)
軟引用相對強引用要弱化一些,可以讓對象豁免一些垃圾收集。當內存空間足夠的時候,垃圾回收器不會回收它。只有當JVM認定內存空間不足時才會去回收軟引用指向的對象。JVM會確保在拋出OOM前清理軟引用指向的對象,而且JVM是很聰明的,會盡可能優先回收長時間閑置不用的軟引用指向的對象,對那些剛構建的或剛使用過的軟引用指向的對象盡可能的保留。基於軟引用的這些特性,軟引用可以用來實現很多內存敏感點的緩存場景,即如果內存還有空閑,可以暫時緩存一些業務場景所需的數據,當內存不足時就可以清理掉,等后面再需要時,可以重新獲取並再次緩存。這樣就確保在使用緩存提升性能的同時,不會導致耗盡內存。
軟引用通常可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; //有時候會返回null sf.get();
通過上面的代碼可以看出sf是對obj的一個軟引用,當sf對象還沒有被銷毀前,sf.get()可以獲取到這個對象,如果已被銷毀,則返回null。
正確使用軟引用的示例代碼如下:
SoftReference<List<Foo>> ref = new SoftReference<List<Foo>>(new LinkedList<Foo>()); // somewhere else in your code, you create a Foo that you want to add to the list List<Foo> list = ref.get(); if (list != null) { list.add(foo); } else { // list is gone; do whatever is appropriate }
在使用軟引用的時候必須檢查引用是否為null。因為垃圾收集器可能在任意時刻回收軟引用,如果不做是否null的判斷,可能會出現NullPointerException的異常。
3.弱引用(weak reference)
弱引用指向的對象是一種十分臨近finalize狀態的情況,當弱引用被清除的時候,就符合finalize的條件了。弱引用與軟引用最大的區別就是弱引用比軟引用的生命周期更短暫。垃圾回收器會掃描它所管轄的內存區域的過程中,只要發現弱引用的對象,不管內存空間是否有空閑,都會立刻回收它。如同前面我說過的,具體的回收時機還是要看垃圾回收策略的,因此那些弱引用的對象並不是說只要達到弱引用狀態就會立馬被回收。
基於弱引用的這些特性,弱引用同樣可以應用在很多需要緩存的場景。
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null; //有時候會返回null wf.get(); //返回是否被垃圾回收器標記為即將回收的垃圾 wf.isEnQueued();
4.幻象引用(phantom reference)
幻象引用,也有被說成是虛引用或幽靈引用。幻象引用並不會決定對象的生命周期。即如果一個對象僅持有虛引用,就相當於沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。不能通過它訪問對象,幻象引用僅僅是提供了一種確保對象被finalize以后,做某些事情的機制(如做所謂的Post-Mortem清理機制),也有人利用幻象引用監控對象的創建和銷毀。
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj); obj=null; //永遠返回null pf.get(); //返回是否從內存中已經刪除 pf.isEnQueued();
幻象引用的get方法永遠返回null,主要用於檢查對象是否已經從內測中刪除。
通過上面對四種引用類型的分析,你可能發現對象的可達性是JVM垃圾收集器決定如何處理對象的一個重要考慮指標。
所有引用類型都是抽象類java.lang.ref.Reference的子類,子類里提供了get()方法。通過上面的分析中可以得知,除了幻象引用(因為get永遠返回null),如果對象還沒有被銷毀,都可以通過get方法獲取原有對象。其實有個非常關鍵的注意點,利用軟引用和弱引用,我們可以將訪問到的對象,重新指向強引用,也就是人為的改變了對象的可達性狀態。所以對於軟引用、弱引用之類,垃圾收集器可能會存在二次確認的問題,以確保處於弱引用狀態的對象沒有改變為強引用。
但是有個問題,如果我們錯誤的保持了強引用(比如,賦值給了static變量),那么對象可能就沒有機會變回類似弱引用的可達性狀態了,就會產生內存泄露。所以,檢查弱引用指向對象是否被垃圾收集,也是診斷是否有特定內存泄露的一個思路,我們的框架使用到弱引用又懷疑有內存泄露,就可以從這個角度檢查。
對於軟引用、弱引用、幻象引用可以配合引用隊列(ReferenceQueue)來使用,特別是幻象引用,get方法只返回null,如果再不指定引用隊列,基本就沒有任何意義了。
上面分析了四種引用類型的使用,熟悉這幾種應用類型對深入理解JVM也大有裨益。
參考:
http://www.kdgregory.com/index.php?page=java.refobj
極客時間《Java核心技術36講》