瞭解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來判斷是否有弱引用和虛引用的對象要被回收,以便及時做出如數據清理等額外的處理。

最後,我們再借一張圖,總結4種引用的對比:
圖片來自:http://developer.51cto.com/art/200906/128189.htm
這裏寫圖片描述

發佈了204 篇原創文章 · 獲贊 280 · 訪問量 85萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章