JVM:判斷一個Java物件是否存活
前言
Java
在接下來的日子,我會推出一系列講解 JVM
的文章,具體如下;感興趣可持續關注 ofollow,noindex">Carson_Ho的安卓開發筆記
示意圖
目錄
示意圖
1. 判斷方式
- 垃圾收集器對
Java
堆裡的物件 是否進行回收的判斷準則:Java
物件是存活 or 死亡
判斷物件為死亡才會進行回收
- 在
Java
虛擬機器中,判斷物件是否存活有2種方法:- 引用計數法
- 引用鏈法(可達性分析法)
下面會進行詳細介紹。
2. 引用計數法
2.1 方式描述
Java
2.2 判斷物件存活準則
當計數器不為 0 時,判斷該物件存活;否則判斷為死亡(計數器 = 0)。
2.3 優點
- 實現簡單
- 判斷高效
2.4 缺點
- 無法解決 物件間相互迴圈引用 的問題
即該演算法存在判斷邏輯的漏洞
- 具體描述
<-- 背景 --> // 物件objA 和 objB 都有欄位 name // 兩個物件相互進行引用,除此之外這兩個人物件沒有任何引用 objA.name = objB; objB.name = objA; <-- 問題 --> // 實際上這兩個物件已經不可能再被訪問,應該要被垃圾收集器進行回收 // 但因為他們相互引用,所以導致計數器不為0,這導致引用計數演算法無法通知垃圾收集器回收該兩個物件
正由於該演算法存在判斷邏輯漏洞,所以 Java
虛擬機器沒有采用該演算法判斷 Java
是否存活。
3. 引用鏈法(可達性分析法)
- 很多主流商用語言(如
Java
、C#
)都採用 引用鏈法 判斷Java
物件是否存活。 - 含3個步驟:
- 可達性分析
- 第一次標記 & 篩選
- 第二次標記 & 篩選
3.1 可達性分析
a. 方式描述
將一系列的 GC Roots
物件作為起點,從這些起點開始向下搜尋。
- 可作為
GC Root
的物件有:
1.Java
虛擬機器棧(棧幀的本地變量表)中引用的物件
2.本地方法棧 中JNI
引用物件
3.方法區 中常量、類靜態屬性引用的物件 - 向下搜尋的路徑 = 引用鏈
如下圖:
示意圖
b. 判斷 物件是否可達 標準
當一個物件到 GC Roots
沒有任何引用鏈相連時,則判斷該物件不可達
沒有任何引用鏈相連 = GC Root
到物件不可達 = 物件不可用
示意圖
特別注意
- 可達性分析 僅僅只是判斷物件是否可達,但還不足以判斷物件是否存活 / 死亡
- 當在 可達性分析 中判斷不可達的物件,只是“被判刑” = 還沒真正死亡
不可達物件會被放在”即將回收“的集合裡。
- 要判斷一個物件真正死亡,還需要經歷兩個階段:
- 第一次標記 & 篩選
- 第二次標記 & 篩選
3.2 第一次標記 & 篩選
- 物件 在 可達性分析中 被判斷為不可達後, 會被第一次標記 & 準備被篩選
a. 不篩選:繼續留在 ”即將回收“的集合裡,等待回收;
b. 篩選:從 ”即將回收“的集合取出
- 篩選的標準:該物件是否有必要執行
finalize()
方法- 若有必要執行(人為設定),則篩選出來,進入下一階段(第二次標記 & 篩選);
- 若沒必要執行,判斷該物件死亡,不篩選 並等待回收
當物件無 finalize()
方法 或 finalize()
已被虛擬機器呼叫過,則視為“沒必要執行”
3.3 第二次標記 & 篩選
當物件經過了第一次的標記 & 篩選,會被進行 第二次標記 & 準備被進行 篩選
a. 方式描述
該物件會被放到一個 F-Queue
佇列中,並由 虛擬機器自動建立、優先順序低的 Finalizer
執行緒去執行 佇列中該物件的 finalize()
-
finalize()
只會被執行一次 - 但並不承諾等待
finalize()
執行結束。這是為了防止finalize()
執行緩慢 / 停止 使得F-Queue
佇列其他物件永久等待。
b. 篩選標準
在執行 finalize()
過程中, 若物件依然沒與引用鏈上的 GC Roots
直接關聯 或 間接關聯(即關聯上與 GC Roots
關聯的物件) ,那麼該物件將被判斷死亡,不篩選(留在”即將回收“集合裡) 並 等待回收
3.4 總結
3步驟 + 以下流程
示意圖
4. 總結
- 本文全面講解判斷Java物件存活的方式
- 在接下來的日子,我會推出一系列講解
JVM
的文章,具體如下;感興趣可持續關注 Carson_Ho的安卓開發筆記
示意圖