在 JVM 中,有兩個非常重要的知識點,一個是 JVM 的記憶體佈局(JVM 執行時的資料區域),另一個就是垃圾回收。而垃圾回收中又有兩個重要的知識點,一個是如何確定 JVM 中的垃圾物件,另一個是使用不同的垃圾收集器進行垃圾回收。而本篇要討論的是前者,後面的內容咱們下一篇再聊。
垃圾物件的判定有兩種常用的演演算法:參照計數器演演算法和可達性分析演演算法。
參照計數演演算法(Reference Counting) 屬於垃圾收集器的早期實現演演算法了,它指的是在建立物件時關聯一個與之相對應的計數器,當此物件被使用時加 1,相反銷燬時 -1。當此計數器為 0 時,則表示此物件未使用,可以被垃圾收集器回收。
參照計數演演算法的優缺點很明顯,其優點是垃圾回收比較及時,實時性比較高,只要物件計數器為 0,則可以直接進行回收操作;而缺點是無法解決迴圈參照的問題,比如以下程式碼:
public class RefCounterTest {
// 物件 A
static class RefObjectA {
private RefObjectB refObjectB;
public void setRefObjectB(RefObjectB refObjectB) {
this.refObjectB = refObjectB;
}
}
// 物件 B
static class RefObjectB {
private RefObjectA refObjectA;
public void setRefObjectA(RefObjectA refObjectA) {
this.refObjectA = refObjectA;
}
}
// 測試程式碼
public static void main(String[] args) {
RefObjectA objectA = new RefObjectA();
RefObjectB objectB = new RefObjectB();
objectA.setRefObjectB(objectB);
objectB.setRefObjectA(objectA);
objectA = null;
objectB = null;
}
}
如以上程式碼所示,即使是將 main 方法中的 objectA 和 objectB 都設定為 null,也就是這兩個物件都徹底不使用了,但是因為二者存在相互參照的關係,所以它們所對應的物件計數器不為 0,這樣迴圈參照導致垃圾資料無法被清除的事件就產生了。
可達性分析演演算法(Reachability Analysis) 是目前主流虛擬機器器中,使用最廣泛的判斷垃圾物件的實現演演算法,它指的是從物件的起點(GC Roots)開始向下搜尋,如果物件到 GC Roots 沒有任何參照鏈相連時,也就是說此物件到 GC Roots 不可達時,則表示此物件可以被垃圾回收器所回收,如下圖所示:
在 Java 語言中,可作為根節點(GC Roots)的物件有以下 4 類:
不管是參照計數法還是可達性分析演演算法都與物件的「參照」有關,這說明物件的參照決定了物件的生死,而 Java 中的參照也比較複雜,它從 JDK 1.2 之後,(參照)分成了以下 4 種型別:
Object obj = new Object()
這類參照,只要強參照還在,垃圾收集器永遠不會回收掉被參照的物件;垃圾物件的判定有兩種常用的演演算法:參照計數器演演算法和可達性分析演演算法。其中參照計數器演演算法實現簡單、執行高效,但是存在迴圈參照的問題,所以主流的虛擬機器器使用的都是可達性分析演演算法,可達性分析演演算法是從物件的根節點 GC Roots 向下搜尋,如果根節點相連就是正常的物件,否則為垃圾物件可以被垃圾回收器回收。
本文已收錄到 Gitee 開源倉庫《Java 面試指南》,其中包含的內容有:Redis、JVM、並行、並行、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、訊息佇列等模組。Java 面試有它就夠了:超全 Java 常見面試題,持續更新...