1. 程式人生 > >JAVA語言核心精講3-強引用、軟引用、弱引用、幻象引用的區別

JAVA語言核心精講3-強引用、軟引用、弱引用、幻象引用的區別

強引用、軟引用、弱引用、幻象引用有什麼區別?具體使用場景是什麼?

典型回答

不同的引用型別,主要體現的是物件不同的可達性(reachable)狀態和對垃圾收集的影響

所謂強引用("Strong" Reference),特點:我們平常典型編碼Object obj = new Object()中的obj就是強引用。通過關鍵字new建立的物件所關聯的引用就是強引用。 當JVM記憶體空間不足,JVM寧願丟擲OutOfMemoryError執行時錯誤(OOM),使程式異常終止,也不會靠隨意回收具有強引用的“存活”物件來解決記憶體不足的問題。對於一個普通的物件,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為 null,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略。

軟引用(SoftReference),特點:軟引用通過SoftReference類實現。 軟引用的生命週期比強引用短一些。只有當 JVM 認為記憶體不足時,才會去試圖回收軟引用指向的物件:即JVM 會確保在丟擲 OutOfMemoryError 之前,清理軟引用指向的物件。軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。後續,我們可以呼叫ReferenceQueue的poll()方法來檢查是否有它所關心的物件被回收。如果佇列為空,將返回一個null,否則該方法返回佇列中前面的一個Reference物件。


應用場景:軟引用通常用來實現記憶體敏感的快取。如果還有空閒記憶體,就可以暫時保留快取,當記憶體不足時清理掉,這樣就保證了使用快取的同時,不會耗盡記憶體。

弱引用(WeakReference)弱引用通過WeakReference類實現。 弱引用的生命週期比軟引用短。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快回收弱引用的物件。弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。

應用場景:弱應用同樣可用於記憶體敏感的快取。

對於幻象引用,特點:虛引用也叫幻象引用,通過PhantomReference類來實現。無法通過虛引用訪問物件的任何屬性或函式。幻象引用僅僅是提供了一種確保物件被 finalize 以後,做某些事情的機制。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue); 
程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。如果程式發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取一些程式行動。
應用場景:可用來跟蹤物件被垃圾回收器回收的活動,當一個虛引用關聯的物件被垃圾收集器回收之前會收到一條系統通知。

考點分析

這道面試題,屬於既偏門又非常高頻的一道題目。說它偏門,是因為在大多數應用開發中,很少直接操作各種不同引用,雖然我們使用的類庫、框架可能利用了其機制。它被頻繁問到,是因為這是一個綜合性的題目,既考察了我們對基礎概念的理解,也考察了對底層物件生命週期、垃圾收集機制等的掌握。

充分理解這些引用,對於我們設計可靠的快取等框架,或者診斷應用 OOM 等問題,會很有幫助。比如,診斷 MySQL connector-j 驅動在特定模式下(useCompression=true)的記憶體洩漏問題,就需要我們理解怎麼排查幻象引用的堆積問題。

知識擴充套件

1. 物件可達性狀態流轉分析

首先,請你看下面流程圖,我這裡簡單總結了物件生命週期和不同可達性狀態,以及不同狀態可能的改變關係,可能未必 100% 嚴謹,來闡述下可達性的變化。

我來解釋一下上圖的具體狀態,這是 Java 定義的不同可達性級別(reachability level),具體如下:

  • 強可達(Strongly Reachable),就是當一個物件可以有一個或多個執行緒可以不通過各種引用訪問到的情況。比如,我們新建立一個物件,那麼建立它的執行緒對它就是強可達。

  • 軟可達(Softly Reachable),就是當我們只能通過軟引用才能訪問到物件的狀態。

  • 弱可達(Weakly Reachable),類似前面提到的,就是無法通過強引用或者軟引用訪問,只能通過弱引用訪問時的狀態。這是十分臨近 finalize 狀態的時機,當弱引用被清除的時候,就符合 finalize 的條件了。

  • 幻象可達(Phantom Reachable),上面流程圖已經很直觀了,就是沒有強、軟、弱引用關聯,並且 finalize 過了,只有幻象引用指向這個物件的時候。

  • 當然,還有一個最後的狀態,就是不可達(unreachable),意味著物件可以被清除了。

判斷物件可達性,是 JVM 垃圾收集器決定如何處理物件的一部分考慮。

所有引用型別,都是抽象類 java.lang.ref.Reference 的子類,你可能注意到它提供了 get() 方法:

除了幻象引用(因為 get 永遠返回 null),如果物件還沒有被銷燬,都可以通過 get 方法獲取原有物件。這意味著,利用軟引用和弱引用,我們可以將訪問到的物件,重新指向強引用,也就是人為的改變了物件的可達性狀態!這也是為什麼我在上面圖裡有些地方畫了雙向箭頭。

所以,對於軟引用、弱引用之類,垃圾收集器可能會存在二次確認的問題,以保證處於弱引用狀態的物件,沒有改變為強引用。