1. 程式人生 > >Android中的引用型別(強引用,弱引用,軟引用,虛引用)

Android中的引用型別(強引用,弱引用,軟引用,虛引用)

Android中的物件有著4種引用型別,垃圾回收器對於不同的引用型別有著不同的處理方式,瞭解這些處理方式有助於我們避免寫出會導致記憶體洩露的程式碼。

在Java中,一切都被視為物件,引用則是用來操縱物件的途徑。

物件和引用之間的關係可以用遙控器(引用)來操縱電視機(物件)這個場景來理解。只要手持這個遙控器,就能保持與電視機的連線。當我們想要改變頻道或者音量時,實際操控的是遙控器(引用),再由遙控器(引用)來調控電視機(物件),達到操控的目的。

當我們嘗試在一個未指向任何物件的引用上去操作物件時,就會遇到經典的空指標異常(NullPointerException)。可以理解成我們手持遙控器,房間裡卻沒有電視機可與之物件(沒有可以用來操控的物件)

GC與記憶體洩露

Java的一個重要優點就是通過垃圾收集器(Garbage Collection,GC)自動管理記憶體的回收,開發者不需要通過呼叫函式來釋放記憶體。在Java中,記憶體的分配是由程式分配的,而記憶體的回收是由GC來完成。
GC為了能夠正確釋放物件,會監控每一個物件的執行狀態,包括物件的申請、引用、被引用、賦值等,GC都需要進行監控。監視物件狀態是為了更加準確地、及時地釋放物件,而釋放物件的根本原則就是該物件不再被引用。

Android中採用了標註與清理(Mark and Sweep)回收演算法:
從”GC Roots”集合開始,將記憶體整個遍歷一次,保留所有可以被GC Roots直接或間接引用到的物件,而剩下的物件都當作垃圾對待並回收。

結合上文所述,記憶體洩露指的是:

我們不再需要的物件資源仍然與GC Roots存在可達路徑,導致該資源無法被GC回收。

Android中的物件有著4種引用型別,垃圾回收器對於不同的引用型別有著不同的處理方式,瞭解這些處理方式有助於我們避免寫出會導致記憶體洩露的程式碼。

Strong reference(強引用)

強引用我們最常用的一種引用型別。當我們使用new關鍵字去新建一個物件的時候,建立的就是強引用。

當一個物件具有強引用,那麼垃圾回收器是絕對不會的回收和銷燬它的。物件的強引用可以在程式中到處傳遞。很多情況下,會同時有多個引用指向同一個物件。

強引用的存在限制了物件在記憶體中的存活時間。假如物件A中包含了一個物件B的強引用,那麼一般情況下,物件B的存活時間就不會短於物件A。如果物件A沒有顯式的把物件B的引用設為null的話,就只有當物件A被垃圾回收之後,物件B才不再有引用指向它,才可能獲得被垃圾回收的機會。

在Java中,非靜態內部類會在其整個生命週期中持有對它外部類的強引用

WeakReference (弱引用)

弱引用通過類WeakReference來表示。弱引用並不能阻止垃圾回收。如果使用一個強引用的話,只要該引用存在,那麼被引用的物件是不能被回收的。弱引用則沒有這個問題。在垃圾回收器執行的時候,如果對一個物件的所有引用都是弱引用的話,該物件會被回收。

需要注意的是,GC回收的是物件,在垃圾回收器執行的時候,如果對一個物件的所有引用都是弱引用的話,該物件會被回收。

SoftReference(軟引用)

我們可以把軟引用理解成一種稍強的弱引用。使用類SoftReference來表示。

很多人可能會把弱引用和軟引用搞混,注意他們的區別在於:如果一個物件只具有軟引用,若記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,才會回收這些物件的記憶體。

而只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。

所以從引用的強度來講: 強引用 > 軟引用 > 弱引用。

表面上看來,軟引用非常適合於建立快取。當系統記憶體不足的時候,快取中的內容是可以被釋放的。

但是,在實踐中,使用軟引用作為快取時效率是比較低的,系統並不知道哪些軟引用指向的物件應該被回收,哪些應該被保留。過早被回收的物件會導致不必要的工作,比如Bitmap要重新從SdCard或者網路上載入到記憶體。

所以使用軟引用去快取物件,雖然確實可以避免OOM問題,卻不適用於某些場景。在Android開發中,一種更好的選擇是使用LruCache,LRU是Least Recently Used的縮寫,即“最近最少使用”,它的內部會維護一個固定大小的記憶體,當記憶體不足的時候,會根據策略把最近最少使用的資料移除,讓出記憶體給最新的資料。具體實現有興趣的同學可以自行研究。

PhantomReference(虛引用)

一個只被虛引用持有的物件可能會在任何時候被GC回收。虛引用對物件的生存週期完全沒有影響,也無法通過虛引用來獲取物件例項,僅僅能在物件被回收時,得到一個系統通知(只能通過是否被加入到ReferenceQueue來判斷是否被GC,這也是唯一判斷物件是否被GC的途徑)。

我們都知道,java的Object類裡面有個finalize方法,它的工作原理是這樣的:一旦垃圾回收器準備好釋放物件佔用的記憶體空間,將首先呼叫其finalize方法,並且在下一次垃圾回收動作發生時,才會真正回收物件佔用的記憶體。但是,問題在於,虛擬機器不能保證finalize何時被呼叫,因為GC的執行時間是不固定的。

使用虛引用就可以解決這個問題,虛引用主要用來跟蹤物件被垃圾回收的活動,主要用來實現比較精細的記憶體使用控制,這對於Android裝置來說是很有意義的。比如,我們可以在確定一個Bitmap被回收後,再去申請另外一個Bitmap的記憶體,通過這種方式可以使得程式所消耗的記憶體維持在一個相對較低且穩定的水平。

虛引用的使用demo可以參考這篇文章:How to use PhantomRefere