騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

眼瞅著國內的疫情逐漸消停,這幾天Emma卻被國外“抗疫行為藝術”驚到掉頭!


最騷的怕是英國開啟“群體免疫(herd immunity)”治療,12號消息一出,海內外一片沸騰:

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

英國網友:國家準備放棄我了嗎?

中國網友:這算個啥解決方案??


什麼是群體免疫Emma就不再做科普了,翻譯成都能懂的話就是:只要60%的英國人都感染了新冠病毒,我們就能獲得群體免疫,病毒也就不會大規模傳播啦!


靠譜不?歷史上,天花和西班牙流感很大程度上是群體免疫起了作用,但代價實在太慘重了!誰想當炮灰?誰不想被現代醫療好好保護?於是英國被全網罵到狗血淋頭。


就像Emma最常說的:

不能實踐的理論都是耍流氓!


所以每次Emma分享完理論,都會另開一章和大家聊聊實踐,今天也不例外~


最近求職市場已經開始回暖,面試的小夥伴們要趕緊查缺補漏。上期咱們講的是強引用、軟引用、弱引用、幻象引用的區別,今天這期知識拓展,就從實踐角度進行分析:



1.對象可達性狀態流轉分析


首先,請你看下面流程圖,我這裡簡單總結了對象生命週期和不同可達性狀態,以及不同狀態可能的改變關係,可能未必100%嚴謹,來闡述下可達性的變化。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

我來解釋一下上圖的具體狀態,這是Java定義的不同可達性級別(reachability level),具體如下:

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇


當然,還有一個最後的狀態,就是不可達(unreachable),意味著對象可以被清除了。


判斷對象可達性,是JVM垃圾收集器決定如何處理對象的一部分考慮。


所有引用類型,都是抽象類java.lang.ref.Reference的子類,你可能注意到它提供了get()方法:

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

除了幻象引用(因為get永遠返回null),如果對象還沒有被銷燬,都可以通過get方法獲取原有對象。這意味著,利用軟引用和弱引用,我們可以將訪問到的對象,重新指向強引用,也就是人為的改變了對象的可達性狀態!這也是為什麼我在上面圖裡有些地方畫了雙向箭頭。


所以,對於軟引用、弱引用之類,垃圾收集器可能會存在二次確認的問題,以保證處於弱引用狀態的對象,沒有改變為強引用。


但是,你覺得這裡有沒有可能出現什麼問題呢?


不錯,如果我們錯誤的保持了強引用(比如,賦值給了static變量),那麼對象可能就沒有機會變回類似弱引用的可達性狀態了,就會產生內存洩漏。所以,檢查弱引用指向對象是否被垃圾收集,也是診斷是否有特定內存洩漏的一個思路,如果我們的框架使用到弱引用又懷疑有內存洩漏,就可以從這個角度檢查。



2.引用隊列(ReferenceQueue)使用


談到各種引用的編程,就必然要提到引用隊列。我們在創建各種引用並關聯到響應對象時,可以選擇是否需要關聯引用隊列,JVM會在特定時機將引用enqueue到隊列裡,我們可以從隊列裡獲取引用(remove方法在這裡實際是有獲取的意思)進行相關後續邏輯。尤其是幻象引用,get方法只返回null,如果再不指定引用隊列,基本就沒有意義了。


看看下面的示例代碼。利用引用隊列,我們可以在對象處於相應狀態時(對於幻象引用,就是前面說的被fnalize了,處於幻象可達狀態),執行後期處理邏輯。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇



3.顯式地影響軟引用垃圾收集


前面泛泛提到了引用對垃圾收集的影響,尤其是軟引用,到底JVM內部是怎麼處理它的,其實並不是非常明確。那麼我們能不能使用什麼方法來影響軟引用的垃圾收集呢?


答案是有的。軟引用通常會在最後一次引用後,還能保持一段時間,默認值是根據堆剩餘空間計算的(以M bytes為單位)。從Java 1.3.1開始,提供了- XX:SoftRefLRUPolicyMSPerMB參數,我們可以以毫秒(milliseconds)為單位設置。比如,下面這個示例就是設置為3秒(3000毫秒)。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

這個剩餘空間,其實會受不同JVM模式影響,對於Client模式,比如通常的Windows 32 bit JDK,剩餘空間是計算當前堆裡空閒的大小,所以更加傾向於回收;而對於server模式JVM,則是根據-Xmx指定的最大值來計算。


本質上,這個行為還是個黑盒,取決於JVM實現,即使是上面提到的參數,在新版的JDK上也未必有效,另外Client模式的JDK已經逐步退出歷史舞臺。所以在我們應用時,可以參考類似設置,但不要過於依賴它。



4.診斷JVM引用情況


如果你懷疑應用存在引用(或fnalize)導致的回收問題,可以有很多工具或者選項可供選擇,比如HotSpot JVM自身便提供了明確的選項(PrintReferenceGC)去獲取相關信息,我指定了下面選項去使用JDK 8運行一個樣例應用:

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

這是JDK 8使用ParrallelGC收集的垃圾收集日誌,各種引用數量非常清晰。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

注意:JDK 9對JVM和垃圾收集日誌進行了廣泛的重構,類似PrintGCTimeStamps和PrintReferenceGC已經不再存在,我在專欄後面的垃圾收集主題裡會更加系統的闡述。



5.Reachability Fence


除了我前面介紹的幾種基本引用類型,我們也可以通過底層API來達到強引用的效果,這就是所謂的設置reachability fence。


為什麼需要這種機制呢?考慮一下這樣的場景,按照Java語言規範,如果一個對象沒有指向強引用,就符合垃圾收集的標準,有些時候,對象本身並沒有強引用,但是也許它的部分屬性還在被使用,這樣就導致詭異的問題,所以我們需要一個方法,在沒有強引用情況下,通知JVM對象是在被使用的。說起來有點繞,我們來看看Java 9中提供的案例。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

方法action的執行,依賴於對象的部分屬性,所以被特定保護了起來。否則,如果我們在代碼中像下面這樣調用,那麼就可能會出現困擾,因為沒有強引用指向我們創建出來的Resource對象,JVM對它進行fnalize操作是完全合法的。

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

類似的書寫結構,在異步編程中似乎是很普遍的,因為異步編程中往往不會用傳統的“執行->返回->使用”的結構。


在Java 9之前,實現類似類似功能相對比較繁瑣,有的時候需要採取一些比較隱晦的小技巧。幸好,java.lang.ref.Reference給我們提供了新方法,它是JEP 193: Variable Handles的一部分,將Java平臺底層的一些能力暴露出來:

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

在JDK源碼中,ReachabilityFence大多使用在Executors或者類似新的HTTP/2客戶端代碼中,大部分都是異步調用的情況。編程中,可以按照上面這個例子,將需要reachability保障的代碼段利用try-fnally包圍起來,在fnally裡明確聲明對象強可達。


今天,我總結了Java語言提供的幾種引用類型、相應可達狀態以及對於JVM工作的意義,並分析了引用隊列使用的一些實際情況,最後介紹了在新的編程模式下,如何利用API去保障對象不被以為意外回收,希望對你有所幫助。


本期福利

硅谷名師免費公開課

關乎你是否能拿到大廠offer的

【大廠真題解析課】

原價¥999 首期報名免費

騰訊面試官:強引用、軟引用、弱引用、幻象引用的區別- 實踐篇

主題:最新頭條真題解析|解題套路

時間:2020/3/20 晚8:00

講師:前硅谷大廠資深工程師【令狐沖】


如何獲得?

關注Emma,回覆「頭條」免費報名


分享到:


相關文章: