您當前的位置:首頁 > 書法

Java引用型別原理剖析

作者:由 蝸牛學苑-蒲老師 發表于 書法時間:2019-03-13

Java中一共有4種引用型別(其實還有一些其他的引用型別比如FinalReference):強引用、軟引用、弱引用、虛引用。其中強引用就是我們經常使用的 Objecta=newObject(); 這樣的形式,在Java中並沒有對應的Reference類。

本篇文章主要是分析軟引用、弱引用、虛引用的實現,這三種引用型別都是繼承於Reference這個類,主要邏輯也在Reference中。

問題

在分析前,先拋幾個問題?

1。網上大多數文章對於軟引用的介紹是:在記憶體不足的時候才會被回收,那記憶體不足是怎麼定義的?什麼才叫記憶體不足?

2。網上大多數文章對於虛引用的介紹是:形同虛設,虛引用並不會決定物件的生命週期。主要用來跟蹤物件被垃圾回收器回收的活動。真的是這樣嗎?

3。虛引用在Jdk中有哪些場景下用到了呢?

Reference

我們先看下 Reference。java中的幾個欄位

Java引用型別原理剖析

一個Reference物件的生命週期如下:

Java引用型別原理剖析

主要分為Native層和Java層兩個部分。

Native層在GC時將需要被回收的Reference物件加入到DiscoveredList中(程式碼在 referenceProcessor。cpp中 process_discovered_references方法),

然後將DiscoveredList的元素移動到PendingList中

(程式碼在 referenceProcessor。cpp中 enqueue_discovered_ref_helper方法),

PendingList的隊首就是Reference類中的pending物件。 具體程式碼就不分析了,有興趣的同學可以看看這篇文章。

http://www。

importnew。com/21628。htm

l

看看Java層的程式碼

Java引用型別原理剖析

流程比較簡單:就是源源不斷的從PendingList中提取出元素,然後將其加入到ReferenceQueue中去,開發者可以透過從ReferenceQueue中poll元素感知到物件被回收的事件。

另外需要注意的是,對於Cleaner型別(繼承自虛引用)的物件會有額外的處理:在其指向的物件被回收時,會呼叫clean方法,該方法主要是用來做對應的資源回收,在堆外記憶體DirectByteBuffer中就是用Cleaner進行堆外記憶體的回收,這也是虛引用在java中的典型應用。

看完了Reference的實現,再看看幾個實現類裡,各自有什麼不同。

SoftReference

Java引用型別原理剖析

軟引用的實現很簡單,就多了兩個欄位: clock和 timestamp。 clock是個靜態變數,每次GC時都會將該欄位設定成當前時間。 timestamp欄位則會在每次呼叫get方法時將其賦值為 clock(如果不相等且物件沒被回收)。

那這兩個欄位的作用是什麼呢?這和軟引用在記憶體不夠的時候才被回收,又有什麼關係呢?

這些還得看JVM的原始碼才行,因為決定物件是否需要被回收都是在GC中實現的。

Java引用型別原理剖析

Java引用型別原理剖析

refs_lists中存放了本次GC發現的某種引用型別(虛引用、軟引用、弱引用等),而 process_discovered_reflist方法的作用就是將不需要被回收的物件從 refs_lists移除掉, refs_lists最後剩下的元素全是需要被回收的元素,最後會將其第一個元素賦值給上文提到過的 Reference。java#pending欄位。

ReferencePolicy一共有4種實現:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。其中NeverClearPolicy永遠返回false,代表永遠不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠返回true,在 referenceProcessor。hpp#setup方法中中可以設定policy為AlwaysClearPolicy,至於什麼時候會用到AlwaysClearPolicy,大家有興趣可以自行研究。

LRUCurrentHeapPolicy和LRUMaxHeapPolicy的shouldclearreference方法則是完全相同:

Java引用型別原理剖析

timestmp_clock就是SoftReference的靜態欄位 clock, java_lang_ref_SoftReference::timestamp(p)對應是欄位 timestamp。如果上次GC後有呼叫 SoftReference#get, interval值為0,否則為若干次GC之間的時間差。

_max_interval則代表了一個臨界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy兩種策略中有差異。

Java引用型別原理剖析

其中 SoftRefLRUPolicyMSPerMB預設為1000,前者的計算方法和上次GC後可用堆大小有關,後者計算方法和(堆大小-上次gc時堆使用大小)有關。

看到這裡你就知道SoftReference到底什麼時候被被回收了,它和使用的策略(預設應該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次呼叫get方法的時間都有關係。

WeakReference

Java引用型別原理剖析

可以看到WeakReference在Java層只是繼承了Reference,沒有做任何的改動。那referent欄位是什麼時候被置為null的呢?要搞清楚這個問題我們再看下上文提到過的 process_discovered_reflist方法:

Java引用型別原理剖析

Java引用型別原理剖析

不管是弱引用還是其他引用型別,將欄位referent置null的操作都發生在 process_phase3中,而具體行為是由 clear_referent的值決定的。而 clear_referent的值則和引用型別相關。

Java引用型別原理剖析

可以看到,對於Soft references和Weak references clear_referent欄位傳入的都是true,這也符合我們的預期:物件不可達後,引用欄位就會被置為null,然後物件就會被回收(對於軟引用來說,如果記憶體足夠的話,在Phase 1,相關的引用就會從refslist中被移除,到Phase 3時refslist為空集合)。

但對於Final references和 Phantom references, clear_referent欄位傳入的是false,也就意味著被這兩種引用型別引用的物件,如果沒有其他額外處理,只要Reference物件還存活,那引用的物件是不會被回收的。Final references和物件是否重寫了finalize方法有關,不在本文分析範圍之內,我們接下來看看Phantom references。

PhantomReference

Java引用型別原理剖析

可以看到虛引用的get方法永遠返回null,我們看個demo。

Java引用型別原理剖析

從以上程式碼中可以看到,虛引用能夠在指向物件不可達時得到一個‘通知’(其實所有繼承References的類都有這個功能),需要注意的是GC完成後,phanRef。referent依然指向之前建立Object,也就是說Object物件一直沒被回收!

而造成這一現象的原因在上一小節末尾已經說了: 對於Finalreferences和Phantomreferences,clear_referent 欄位傳入的時false,也就意味著被這兩種引用型別引用的物件,如果沒有其他額外處理,在GC中是不會被回收的。

對於虛引用來說,從 refQueue。remove();得到引用物件後,可以呼叫 clear方法強行解除引用和物件之間的關係,使得物件下次可以GC時可以被回收掉。

End

針對文章開頭提出的幾個問題,看完分析,我們已經能給出回答:

1.我們經常在網上看到軟引用的介紹是:

在記憶體不足的時候才會回收,那記憶體不足是怎麼定義的?為什麼才叫記憶體不足?

軟引用會在記憶體不足時被回收,記憶體不足的定義和該引用物件get的時間以及當前堆可用記憶體大小都有關係,計算公式在上文中也已經給出。

2.網上對於虛引用的介紹是:

形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。主要用來跟蹤物件被垃圾回收器回收的活動。真的是這樣嗎?

嚴格的說,虛引用是會影響物件生命週期的,如果不做任何處理,只要虛引用不被回收,那其引用的物件永遠不會被回收。所以一般來說,從ReferenceQueue中獲得PhantomReference物件後,如果PhantomReference物件不會被回收的話(比如被其他GC ROOT可達的物件引用),需要呼叫 clear方法解除PhantomReference和其引用物件的引用關係。

3.虛引用在Jdk中有哪些場景下用到了呢?

DirectByteBuffer中是用虛引用的子類 Cleaner。java來實現堆外記憶體回收的,後續會寫篇文章來說說堆外記憶體的裡裡外外。

標簽: 引用  回收  物件  Reference  GC