1. 程式人生 > >瞭解Java的4種引用型別

瞭解Java的4種引用型別

Java引用型別

Java中有兩種型別,值型別和引用型別。其中引用型別有點類似指標,它儲存著物件的地址。通過引用,可以對堆中的物件進行操作。

《深入理解Java虛擬機器 JVM高階特性與最佳實踐》一書3.2.3節中對引用有如下描述:

在JDK 1.2之前,Java中的引用的定義很傳統:如果reference型別的資料中儲存的數值代表的是另一塊記憶體的起始地址,就稱這塊記憶體代表著一個引用……
在JDK 1.2之後,Java對引用的概念進行了擴充,將引用分為強引用、軟引用、弱引用、虛引用四種,這四種引用強度依次逐漸減弱。

強引用(StrongReference)

《Java程式效能優化》一書3.4節中描述,強引用具備以下特點:

  • 強引用可以直接訪問目標物件
  • 強引用所指向的物件在任何時候都不會被系統回收。JVM寧願丟擲OOM異常,也不回收所指向的物件。
  • 強引用可能導致記憶體洩露。

軟引用(SoftReference)

用於描述一些還有用,但是非必需的物件。OOM異常之前,會將這些物件列入回收範圍之中進行第二次回收。如果回收之後還是沒有足夠記憶體,就丟擲OOM異常。

弱引用(WeakReference)

弱引用關聯的物件只能生存到下一次垃圾收集發生之前。也就是,垃圾回收器執行緒開始掃描所管轄的記憶體區域時,一旦發現只具備弱引用的物件,不管當前記憶體空間是否足夠,都會回收它的記憶體。但是由於垃圾回收器執行緒優先順序很低,因此不一定會很快發現只具有弱引用的物件。

這篇部落格還提到:

弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。

虛引用(PhantomReference)

虛引用是最弱的一種引用(也稱為幽靈引用),虛引用不會決定物件的生命週期。

虛引用主要用來跟蹤物件被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之 關聯的引用佇列中。
程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。

閱讀原始碼註釋

檢視Android原始碼裡面JDK的目錄中的java\lang\ref,我們可以看到有如下幾個檔案:

  • Reference.java
  • WeakReference.java
  • SoftReference.java
  • PhantomReference.java
  • FinalizerReference.java
  • ReferenceQueue.java

首先檢視Reference.java檔案,註釋描述:

Provides an abstract class which describes behavior common to all reference
objects. It is not possible to create immediate subclasses of
Reference in addition to the ones provided by this package. It is
also not desirable to do so, since references require very close cooperation
with the system’s garbage collector. The existing, specialized reference
classes should be used instead.

大概意思是說:

提供了一個描述所有引用物件的共同行為的抽象類。不可能建立引用的直接子類,也不希望這麼做。因為引用需要與系統垃圾回收器緊密合作。應該使用現有的,指定的引用類。

Reference類似一個泛型的抽象類,裡面定義了引用的物件,引用佇列等,並定義了引用佇列入隊等方法。

SoftReference繼承自Reference,我們也可以看看裡面的註釋(原文比較長,不貼出來了),大致意思:

實現軟引用,它是三種引用(不包括強引用)中least-weak的引用。一旦垃圾收集器掃描到一個物件obj是softly-reachable,下面的情況就有可能立即或者以後出現:

  • 一個引用的集合ref是確定的,ref包含下面的元素
    • 所有指向obj的軟引用。
    • 所有指向可以從它到obj是strongly reachable的軟引用。
  • 所有在ref中的引用自動被清除;
  • 在同一時間或者未來的某一時間,所有在ref中的引用將入隊到他們相關的引用佇列中,如果有的話。

系統可能延遲清除或者入隊軟引用,但是所有指向軟可到物件的軟引用在執行時丟擲OutOfMemoryError前被清除。
軟引用適合那些從外部不再繼續引用,應該刪除掉,且需要記憶體的時候的情況下用作快取。
SoftReference和WeakReference的不同的是什麼時候決定清除或者入隊引用:

  • SoftReference應該儘可能晚的清除或者入隊,就是說,VM快要記憶體耗盡的時候。
  • WeakReference可能在當清除或者入隊只要weakly-referenced。

檢視Android官網上對SoftReference的介紹,其中提到這樣一個問題:

Avoid Soft References for Caching

In practice, soft references are inefficient for caching. The runtime doesn’t have enough information on which references to clear and which to keep. Most fatally, it doesn’t know what to do when given the choice between clearing a soft reference and growing the heap.
The lack of information on the value to your application of each reference limits the usefulness of soft references. References that are cleared too early cause unnecessary work; those that are cleared too late waste memory.

Most applications should use an android.util.LruCache instead of soft references. LruCache has an effective eviction policy and lets the user tune how much memory is allotted.

意思大致是:

軟引用在實踐中用來做快取是低效的,因為執行時並沒有關於哪個引用要清除或者保留的足夠的資訊,最要命的是當要選擇清除軟引用還是增長棧的時候,它不知道怎麼做。
你的程式的引用的資訊的缺失導致軟引用用處的侷限性。過早清除的引用會導致無用功,太晚清除又會浪費記憶體。
大多數程式應該使用android.util.LruCache來替代軟引用,LruCache有著更高效的回收策略,並讓使用者協調要分配多少記憶體。

對於WeakReference:

WeakReference是三種引用中中間的一個。一旦垃圾收集器決定某個物件obj是weakly-reachable,下面就會發生:

  • A set ref of references is determined.ref contains the following elements:
    • All weak references pointing to obj.
    • All weak references pointing to objects from which obj is either strongly or softly reachable.
  • All objects formerly being referenced by ref become eligible for finalization.
  • At some future point, all references in ref will be enqueued with their corresponding reference queues, if any.

對於PhantomReference:

PhantomReference是三種引用中最弱的引用。一旦垃圾收集器確定一個物件obj是phantom-reachable,它就要入隊。
在相關的佇列裡面,它的referent(引用的物件)是不明確的。這就是說,虛引用的引用佇列必須明確的在程式程式碼中進行理。因此,不與任何引用佇列相關聯的虛引用就不會產生任何影響。
虛引用適合實現物件在垃圾回收之前的必要的cleanup操作。它有時候比Object的finalize()方法更靈活。

我們看原始碼的時候,會注意到PhantomReference的get()方法:

@Override
public T get() {
    return null;
}

這裡方法永遠返回null。這裡就會產生疑惑,返回null的引用有何用?其實這就是它的特點,所以虛引用唯一的用處就是跟蹤referent何時被enqueue到ReferenceQueue中。因為 PhantomReference是在finalize方法執行後回收的,所以可以避免在finalize方法執行後再建立強引用導致無法回收的問題。

另外,參考這篇文章,瞭解GC、 Reference 與 ReferenceQueue 的互動:
>
A、 GC無法刪除存在強引用的物件的記憶體。
B、 GC發現一個只有軟引用的物件記憶體,那麼:
① SoftReference物件的 referent 域被設定為 null ,從而使該物件不再引用 heap 物件。
② SoftReference引用過的 heap 物件被宣告為 finalizable 。
③ 當 heap 物件的 finalize() 方法被執行而且該物件佔用的記憶體被釋放, SoftReference 物件就被新增到它的 ReferenceQueue (如果後者存在的話)。
C、 GC發現一個只有弱引用的物件記憶體,那麼:
① WeakReference物件的 referent 域被設定為 null , 從而使該物件不再引用heap 物件。
② WeakReference引用過的 heap 物件被宣告為 finalizable 。
③ 當heap 物件的 finalize() 方法被執行而且該物件佔用的記憶體被釋放時, WeakReference 物件就被新增到它的 ReferenceQueue (如果後者存在的話)。
D、 GC發現一個只有虛引用的物件記憶體,那麼:
① PhantomReference引用過的 heap 物件被宣告為 finalizable 。
② PhantomReference在堆物件被釋放之前就被新增到它的 ReferenceQueue 。

ReferenceQueue有什麼用處呢?
前面提到,弱引用和虛引用都可以配合ReferenceQueue使用,這樣的話,就可以使用ReferenceQueue來判斷是否有弱引用和虛引用的物件要被回收,以便及時做出如資料清理等額外的處理。