引言
上一篇文章 JVM 基本介紹 我們瞭解了一些基本的 JVM 知識,本篇開始逐步學習垃圾回收,我們都知道既然叫垃圾回收,那回收的就應該是垃圾,可是我們怎麼知道哪些物件是垃圾呢? 哪些物件需要被回收? 什麼時候需要回收呢?
判斷演算法
引用計數演算法
給每個物件設定一個計數器,每當該物件被引用時引用計數器加 1,有引用斷開時引用計數減 1。當引用計數為 0 時表示該物件可以被回收。
這個可以用資料演算法中的圖形表示,物件 A-物件 B-物件 C 都有引用,所以不會被回收,物件 B 由於沒有被引用,沒有路徑可以達到物件 B,物件 B 的引用計數就就是 0,物件 B 就會被回收。
優點
客觀地說,引用計數演算法的實現簡單,判定效率也很高,在大部分情況下它都是一個不錯的演算法,也有一些比較著名的應用案例,例如微軟公司的 COM(Component Object Model)技術、使用 ActionScript 3 的 FlashPlayer、Python 語言和在遊戲指令碼領域被廣泛應用的 Squirrel 中都使用了引用計數演算法進行記憶體管理。
缺點
主流的 Java 虛擬機器裡面沒有選用引用計數演算法來管理記憶體,其中最主要的原因是它很難解決物件之間相互迴圈引用的問題。因為相互引用,計數器值永遠也不會成為 0 ,所以永遠達不到被 GC 回收的條件。
例如下圖:物件A,物件B 迴圈引用,沒有其他的物件引用A和B,則A和B 都不會被回收。
可達性分析演算法
該演算法的原理是:以 GC Roots
的物件作為起始點,然後以該節點為基準開始向下搜尋,搜尋過程中搜尋路徑我們稱之為引用鏈,當一個物件到 GC Roots
沒有任何引用鏈連線的時候,說明該物件是不可用的。
注意:我們在 上邊的所說的引用都是指定的強引用關係。
因為我們知道 Java 中存在四種引用物件,根據引用強度(從上至下依次減弱)可依次劃分為:
強引用
Strong Reference
軟引用
Weak Reference
弱引用
Phantom Reference
虛引用
Soft Reference
詳細的概念大家下去可以自行檢視,此處不再贅述。
可以用作 GC Roots 的物件
方法區 : 類靜態變數引用的物件
方法區 : 常量引用的物件
虛擬機器棧 : 本地變數表中引用的物件
本地方法棧 : JNI (帶 Native 關鍵字)引用的物件
如下圖,物件 D 和根物件之間毫無引用鏈,則會被回收。
由於這種演算法即使存在互相引用的物件,但如果這兩個物件無法訪問到根物件,還是會被回收。如下圖:物件 C 和物件 D 互相引用,但是由於無法訪問根節點,還是會被回收。
不可達是不是就一定會被回收?
答案是不一定。
一個物件在真正被回收之前,需要經歷兩次標記過程:
第一次標記:
如果物件在進行可達性分析之後發現沒有與 GC Roots 相連線的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此物件是否有必要執行 finalize() 方法,此時分兩種情況:
物件沒有覆蓋 finalize() 方法
finalize() 方法已經被虛擬機器呼叫過
在這兩種情況下虛擬機器都認為此時沒有必要執行垃圾回收。
第二次標記:
在第一次標記判定基礎之上,如果判定為有必要執行 finalize() 方法,則虛擬機器會把這個物件放置到一個叫做 F-Queue 的佇列之中,並在之後用 Finalizer 執行緒去執行回收。(此處的執行指的是 虛擬機器會去觸發這個方法,但是並不保證回收成功,也不承諾會等待他執行結束,因為如果有個別物件在 finalize() 方法中執行緩慢甚至發生死迴圈的時候,有可能會導致 F-Queue 佇列中的其他物件發生永久等待,最後導致整個垃圾回收系統崩潰)
總結
引用計數法:簡單高效,但是對於相互迴圈引用的物件無法判斷是否應該被回收
可達性分析:目前大多虛擬機器廠商採用的垃圾回收演算法,通過判斷其他物件是否和根節點之間存在引用鏈來分析是否應該被回收
不可達的物件不一定會立即被回收