深入JVM物件引用
在jdk 1.2以前,建立的物件只有處在可觸及(reachaable)狀態下,才能被程式所以使用,垃圾回收器一旦發現無用物件,便會對其進行回收。但是,在某些情況下,我們希望有些物件不需要立刻回收或者說從全域性的角度來說並沒有立刻回收的必要性。比如快取系統的設計,在記憶體不吃緊或者說為了提高執行效率的情況下,一些暫時不用的物件仍然可放置在記憶體中,而不是立刻進行回收。因此,從jdk 1.2 版本開始,java設計人員把物件的引用細分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)四種級別,主要區別體現在在被GC回收的優先順序上:強引用->軟引用->弱引用->虛引用。也就是說從jdk 1.2開始,垃圾回收器回收物件時,物件的可達性分析需要考慮考慮物件的引用強度,也就是說現在物件的有效性=可達性+引用型別。先來看下類層次結構如下:

image.png
1. 引用的四種類型
1. 強引用(Strong Reference)
在程式碼中普遍使用的,類似Person person=new Person();如果一個物件具有強引用,則無論在什麼情況下,GC都不會回收被引用的物件。當記憶體空間不足時,JAVA虛擬機器寧可丟擲OutOfMemoryError終止應用程式也不會回收具有強引用的物件。
2. 軟引用(Soft Reference)
表示一個物件處在有用但非必須的狀態。如果一個物件具有軟引用,在記憶體空間充足時,GC就不會回收該物件;當記憶體空間不足時,GC會回收該物件的記憶體(回收發生在OutOfMemoryError之前)。
1.Person person=new Person();
2.SoftReference sr=new SoftReference(person);
軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被GC回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中,以便在恰當的時候將該軟引用回收。但是由於GC執行緒的優先順序較低,通常手動呼叫System.gc()並不能立即執行GC,因此弱引用所引用的物件並不一定會被馬上回收。
3. 弱引用(Weak Reference)
用來描述非必須的物件。它類似軟引用,但是強度比軟引用更弱一些:弱引用具有更短的生命.GC在掃描的過程中,一旦發現只具有被弱引用關聯的物件,都會回收掉被弱引用關聯的物件。換言之,無論當前記憶體是否緊缺,GC都將回收被弱引用關聯的物件。
1.Person person=new Person();
2.WeakReference wr=new WeakReference(person);
同樣弱引用也可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被GC回收了,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中,以便在恰當的時候將該弱引用回收。
4. 虛引用(Phantom Reference)
虛引等同於沒有引用,這意味著在任何時候都可能被GC回收,設定虛引用的目的是為了被虛引用關聯的物件在被垃圾回收器回收時,能夠收到一個系統通知。(被用來跟蹤物件被GC回收的活動)虛引用和弱引用的區別在於:虛引用在使用時必須和引用佇列(ReferenceQueue)聯合使用,其在GC回收期間的活動如下:
1.ReferenceQueue queue=new ReferenceQueue();
2.PhantomReference pr=new PhantomReference(object.queue);
也即是GC在回收一個物件時,如果發現該物件具有虛引用,那麼在回收之前會首先該物件的虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是否已經加入虛引用來了解被引用的物件是否被GC回收。
ReferenceQueue和Reference
-
ReferenceQueue含義及作用
通常我們將其ReferenceQueue翻譯為引用佇列,換言之就是存放引用的佇列,儲存的是Reference物件。其作用在於Reference物件所引用的物件被GC回收時,該Reference物件將會被加入引用佇列中(ReferenceQueue)的佇列末尾,這相當於是一種通知機制.當關聯的引用佇列中有資料的時候,意味著引用指向的堆記憶體中的物件被回收。通過這種方式,JVM允許我們在物件被銷燬後,做一些我們自己想做的事情。JVM提供了一個ReferenceHandler執行緒,將引用加入到註冊的引用佇列中
ReferenceQueue常用的方法:
public Reference<? extends T> poll():從佇列中取出一個元素,佇列為空則返回null;
public Reference<? extends T> remove():從佇列中出對一個元素,若沒有則阻塞至有可出隊元素;
public Reference<? extends T> remove(long timeout):從佇列中出對一個元素,若沒有則阻塞至有可出對元
素或阻塞至超過timeout毫秒;
見如下程式碼:

image.png
這段程式碼中,對於Person物件有兩種引用型別,一是person的強引用,而是sr的軟引用。sr強引用了SoftReference物件,該物件軟引用了Person物件。當person被回收時,sr所強引用的物件將會被放到rq的佇列末尾。利用ReferenceQueue可以清除失去了軟引用物件的SoftReference,如下操作:

image.png
-
Reference類
Reference是SoftReference,WeakReference,PhantomReference類的父類,其內部通過一個next欄位來構建了一個Reference型別的單向列表,而queue欄位存放了引用物件對應的引用佇列,若在Reference的子類建構函式中沒有指定,則預設關聯一個ReferenceQueue.NULL佇列。
-
四種引用型別使用場景
強引用型別是在程式碼中普遍存在,無須解釋太多了
軟引用和弱引用:兩者都可以實現快取功能,但軟引用實現的快取通常用在服務端,而在移動裝置中的記憶體更為緊缺,對垃圾回收更為敏感,因此android中的快取通常是用弱引用來實現(比如LruCache)
在開發中有這麼一個場景:使用者資訊查詢。在不考慮對使用者資訊更改的情況下,通常有以下兩種方案來實現:
-
第一次查詢時,讀取資料庫後將使用者資訊存放在記憶體中,以後每次查詢從記憶體中讀取,優點是讀取速度快,在請求次數較少的情況下,記憶體佔用較多。
現在我們用採用第二種方案,基於快取來設計,程式碼如下:
image.png
image.png
image.png
image.png
image.png
這樣簡單的快取就完成了,輸出結果如下:
image.png
4. 到底是什麼引用型別?
如果一個物件有多個引用型別,那在進行垃圾回收時如何判斷物件的可達性呢?其原則如下:
單挑引用鏈的可達性以最弱的一個引用型別來決定;
多條引用鏈的可達性以最強的一個引用型別來決定;
舉例說明,如下圖所示:

image.png
對Object 2進行分析,路徑1-1——>2-1中取最弱的引用,軟引用;路徑1-2——>2-2中取最弱引用,虛引用;在這兩條路徑中取最強引用,軟引用。因此Object 2是最終的引用型別是軟引用。
示例程式碼
我們通過一段程式碼幫助理解這四種:

image.png