偏僻又熱門,引用與引用隊列
引用和引用指向的對象
A reference object(引用,我感覺這個不如直接寫成 reference 更容易理解)is a layer of indirection between your program code and some other object, called a referent(引用指向的對象). Each reference object is constructed around its referent, and the referent cannot be changed.
翻譯起來比較簡單,引用(reference object)是一個間接層,我們的代碼通過引用訪問引用指向的對象(referent)。
relationships between application code, soft/weak reference, and referent
所有的引用類型,都是抽象類 java.lang.ref.Reference 的子類:
這個抽象類提供了 get 方法用來獲取引用指向的對象(referent):
舉個例子:
SoftReference<List<Foo>> ref = new SoftReference<List<Foo>>(new LinkedList<Foo>());
// somewhere else in your code, you create a Foo that you want to add to the list
List<Foo> list = ref.get();
if (list != null) {
list.add(foo);
} else {
// list is gone; do whatever is appropriate
}
四種引用的定義
我想大部分人都可以很輕松的說出引用的定義:如果棧中的變量存儲的數值代表的是另外一塊內存的起始地址,就稱該變量代表了這塊內存 or 這個對象的引用。
在 JDK 1.2 之前,沒有問題,這個定義很正確。
不過現在來看有些過于狹隘了。
舉個例子,我們希望引用能夠描述這樣一類對象:當內存空間還足夠時,就保留在內存之中,如果垃圾收集后內存空間比較緊張,那就拋棄這些對象釋放空間。
對于上述的定義來說,一個對象只有 “被引用” 和 “未被引用” 兩種狀態,對這種情況顯然是無能無力的。
所以,JDK 1.2 之后,Java 對引用的概念進行了擴充,將引用分為以下四種,這 4 種引用的強度依次逐漸減弱,所謂 “強度”,可以這樣簡單理解,引用的強度越強,那么這個被引用的對象就越不容易被垃圾回收器回收掉:
1)強引用,Strongly Re-ference
強引用隨處可見,就是最傳統的 “引用” 的定義,通過 new 進行的引用賦值,即類似User user = new User() 這種引用關系。
只要還有強引用指向一個對象,就能表明對象還 “活著”,垃圾收集器永遠不會碰這種對象。
換句話說,當內存空間不足的時候,JVM 寧可拋出 OOM,使程序異常終止,也不會回收具有強引用的對象。
2)軟引用,Soft Reference
軟引用就對應我們上面舉的那個例子,可以讓對象豁免一些垃圾收集,用來描述一些還有用、但非必須的對象。
如果內存空間足夠,那么軟引用就不會被回收掉,但是如果快要發生 OOM 了,那么 JVM 就會對這些軟引用進行回收釋放空間,如果對這些軟引用回收完了之后還是沒有足夠的內存,才會拋出 OOM。
在 JDK 1.2 之后提供了 SoftReference 類來實現軟引用。
3)弱引用,Weak Reference
弱引用也是用來描述那些非必須對象,但是它的強度比軟引用更弱一些。
如果你創建了一個僅持有弱引用的對象,那么下一次垃圾收集發生的時候,無論當前內存是否足夠,這個對象都會被回收掉。
換句話說,被弱引用關聯的對象只能生存到下一次垃圾收集發生為止。
在 JDK 1.2 之后提供了 WeakReference 類來實現弱引用。
4)虛引用,Phantom Reference
虛引用也稱為 幽靈引用、幻影引用、幻象引用,它是最弱的一種引用關系。
如果一個對象僅持有幻像引用,那么它就和沒有任何引用一樣,對其生存時間沒有任何影響,我們也無法通過幻像引用來取得一個對象實例(看下圖,它的 get 方法永遠返回 null)。
虛引用的 get 方法
滑稽了,那幻像引用有啥用?
事實上,我們可以通過為一個對象設置幻像引用關聯從而跟蹤這個對象被垃圾回收的活動(詳細見下文解釋)。
在 JDK 1.2 之后提供了 PhantomReference 類來實現幻像引用。
對象的生命周期
在 JDK1.2 之前,一個對象的生命周期(object life cycle)可以簡單的用下圖表示:
object life-cycle, without reference objects
而在 JDK1.2 中,引入了 java.lang.ref 包,一個對象的生命周期中新增了三個狀態(stage):
可以看到,除了強引用對應的強可達狀態(strongly reachable)之外,額外添加了個三個狀態,分別對應軟引用、弱引用和虛引用(幻像引用):
- softly reachable,軟可達:就是當我們只能通過軟引用才能訪問到對象的狀態。
- weakly reachable,弱可達:就是無法通過強引用或者軟引用訪問,只能通過弱引用訪問時的狀態。
- phantom reachable,幻象可達:上面流程圖已經很直觀了,就是沒有強、軟、弱引用關聯,并且被回收掉了,只有幻像引用指向這個對象的時候。
除了幻像引用(因為 get 永遠返回 null),如果對象還沒有被銷毀,都可以通過 get 方法獲取原有對象。這意味著,利用軟引用和弱引用,我們可以將訪問到的對象,重新指向強引用,也就是人為的改變了對象的可達性狀態!這也是為什么上面圖里有些地方畫了雙向箭頭。
引用隊列
引用隊列 ReferenceQueue 是用來配合引用工作的,最常與幻像引用一起使用,因為幻像引用的構造函數必須指定引用隊列,而其他引用類型沒有引用隊列一樣可以運行。
當某個被引用的對象(referent)被回收的時候,JVM 會將指向它的引用(reference)加入到引用隊列的隊列末尾,這相當于是一種通知機制。這個操作其實是由 ReferenceHandler 守護線程來做的,這個守護線程是在 Reference 靜態代碼塊中建立并且運行的線程,所以只要 Reference 這個父類被初始化,該線程就會創建和運行,它的運行方法中依賴了比較多的本地 (native) 方法:
由于 ReferenceHandler 是守護線程,除非 JVM 進程終結,否則它會一直在后臺運行(注意它的 run() 方法里面使用了死循環)。
實際上就是調用了引用隊列的 enqueue 方法來執行入隊操作:
這樣,我們可以通過 ReferenceQueue 中的元素(引用)來知道哪些對象(被引用的對象)被回收掉了,通過這種方式,我們就可以在對象被回收掉之后,做一些我們自己想做的事情。
這也就是為什么說幻像引用存在的唯一作用就是跟蹤對象被垃圾回收的活動。
另外,ReferenceQueue 提供了三種方法來彈出隊頭元素:
- poll():用于移除并返回該隊列中的下一個引用對象,如果隊列為空,則返回null;
- remove():用于移除并返回該隊列中的下一個引用對象,該方法會在隊列返回可用引用對象之前一直阻塞;
- remove (long timeout):用于移除并返回隊列中的下一個引用對象。該方法會在隊列返回可用引用對象之前一直阻塞,或者在超出指定超時后結束。如果超出指定超時,則返回null。如果指定超時為0,意味著將無限期地等待。
不同引用類型的應用場景
軟引用的應用:斷路器
斷路器,Circuit Breaker
- A better use of soft references is to provide a "circuit breaker" for memory allocation: put a soft reference between your code and the memory it allocates, and you avoid the dreaded OutOfMemoryError.
舉個例子,下面這段 JDBC 代碼,邏輯是查詢數據庫的多行數據。
往比較極端的情況想,如果查詢到的數據有一百萬行,但你的系統的可用內存資源已經不足以裝得下這一百萬行數據,此時程序肯定就拋錯誤了。
這個時候軟引用的價值就體現出來了:如果在查詢數據期間 JVM 已經耗盡了內存,那么被軟引用指向的對象的內存就會被釋放掉從而給新的數據挪出空間,同時在業務線程上我們可以拋出自定義異常以便我們進行程序的后續處理:
弱引用的應用:ThreadLocal 的 ThreadLocalMap 實現
大名鼎鼎,這個本文就不多說了,后續會開文章詳細解釋。
虛引用的應用:數據庫連接池
數據庫連接池 Connection Pool 應該具備的一個優點就是能夠有效的避免連接資源泄露,同時能夠對連接資源進行回收:
下面這個類可以不用怎么看,不過有一點值得注意,用戶使用該連接池時業務線程拿到的連接對象正是這個PooledConnection 對象,而不是真正的 Connection 對象。
重點看下下面這個類的實現:
如果引用隊列中能夠拿到引用,說明連接對象被 GC 掉了,此時我們就應該對連接池執行相應的清理邏輯(重點注意下面的 releaseConnection 方法):
看起來挺復雜,其實本質上就是圍繞著虛引用的特性:你不能通過它訪問對象,但是它結合引用隊列提供了一種對象被回收以后做某些事情的機制。