前言
- 如何判斷一個Java對象是否存活對於垃圾回收、防止內存泄漏等十分重要
- 本文將全面講解判斷
Java
對象存活的方式,希望你們會喜歡
目錄
1. 判斷方式
- 垃圾收集器對
Java
堆裏的對象 是否進行回收的判斷準則:Java
對象是存活 or 死亡
判斷對象爲死亡纔會進行回收
- 在
Java
虛擬機中,判斷對象是否存活有2種方法:- 引用計數法
- 引用鏈法(可達性分析法)
下面會進行詳細介紹。
2. 引用計數法
2.1 方式描述
- 給
Java
對象添加一個引用計數器 - 每當有一個地方引用它時,計數器 +1;引用失效則 -1;
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
的文章,具體如下;感興趣的同學可以繼續關注本人運營的:CSDN技術博客