面試必問:JVM 如何確定死亡物件?

2023-02-16 09:00:40

在 JVM 中,有兩個非常重要的知識點,一個是 JVM 的記憶體佈局(JVM 執行時的資料區域),另一個就是垃圾回收。而垃圾回收中又有兩個重要的知識點,一個是如何確定 JVM 中的垃圾物件,另一個是使用不同的垃圾收集器進行垃圾回收。而本篇要討論的是前者,後面的內容咱們下一篇再聊。

垃圾物件的判定有兩種常用的演演算法:參照計數器演演算法和可達性分析演演算法。

1.參照計數器演演算法

參照計數演演算法(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,這樣迴圈參照導致垃圾資料無法被清除的事件就產生了。

2.可達性分析演演算法

可達性分析演演算法(Reachability Analysis) 是目前主流虛擬機器器中,使用最廣泛的判斷垃圾物件的實現演演算法,它指的是從物件的起點(GC Roots)開始向下搜尋,如果物件到 GC Roots 沒有任何參照鏈相連時,也就是說此物件到 GC Roots 不可達時,則表示此物件可以被垃圾回收器所回收,如下圖所示:

在 Java 語言中,可作為根節點(GC Roots)的物件有以下 4 類:

  1. Java 虛擬機器器棧中的參照物件,也就是 Java 虛擬機器器棧幀中,本地變數表所儲存的(參照)物件。在 Java 虛擬機器器棧幀中儲存的物件都是將來執行時,要使用的物件,所以和參照物件相關的物件都不能被回收;
  2. 本地方法棧中的參照物件和 Java 虛擬機器器棧中的參照物件類似,也不能被回收;
  3. 方法區中類靜態屬性參照的物件也可以作為 GC Roots;
  4. 方法區中常數參照的物件也可以作為 GC Roots。因為常數是儲存在常數池中的,屬於全域性可使用的物件,所以也能作為 GC Roots。

3.有關「參照」

不管是參照計數法還是可達性分析演演算法都與物件的「參照」有關,這說明物件的參照決定了物件的生死,而 Java 中的參照也比較複雜,它從 JDK 1.2 之後,(參照)分成了以下 4 種型別:

  • 強參照:在程式碼中普遍存在的,類似 Object obj = new Object() 這類參照,只要強參照還在,垃圾收集器永遠不會回收掉被參照的物件
  • 軟參照:是一種相對強參照弱化一些的參照,可以讓物件豁免一些垃圾收集,只有當 JVM 認為記憶體不足時,才會去試圖回收軟參照指向的物件,JVM 會確保在丟擲 OutOfMemoryError 之前,清理軟參照指向的物件
  • 弱參照:非必需物件,但它的強度比軟參照更弱,被弱參照關聯的物件只能生存到下一次垃圾收集發生之前
  • 虛參照:也稱為幽靈參照或幻影參照,是最弱的一種參照關係,無法通過虛參照來獲取一個物件範例,為物件設定虛參照的目的只有一個,就是當著個物件被收集器回收時收到一條系統通知

總結

垃圾物件的判定有兩種常用的演演算法:參照計數器演演算法和可達性分析演演算法。其中參照計數器演演算法實現簡單、執行高效,但是存在迴圈參照的問題,所以主流的虛擬機器器使用的都是可達性分析演演算法,可達性分析演演算法是從物件的根節點 GC Roots 向下搜尋,如果根節點相連就是正常的物件,否則為垃圾物件可以被垃圾回收器回收。

本文已收錄到 Gitee 開源倉庫《Java 面試指南》,其中包含的內容有:Redis、JVM、並行、並行、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、訊息佇列等模組。Java 面試有它就夠了:超全 Java 常見面試題,持續更新...