JVM必備基礎知識(三)-- GC垃圾回收機制

ClericYi發表於2020-02-21

JVM必備基礎知識(三)-- GC垃圾回收機制

本章內容是對《深入理解Java虛擬機器:JVM高階特性和最佳實踐》的理解和概括。

前言

前文中我們講過了類載入器和雙親委派,那麼接下來介紹的就是GC垃圾回收機制。

Java記憶體模型

在此之前我們需要知道GC回收機制回收的是什麼?他們的儲存形式是什麼樣的?等等一系列問題。所以引入了記憶體模型的概念。

Java記憶體模型

5大區域各自的作用:

  1. 程式計數器:指示當前執行緒所執行的位元組碼執行到了第幾行。
  2. 虛擬機器棧:為執行的方法建立棧幀,儲存了區域性變數表、操作站、動態連結、方法出口等資訊,主要用來執行Java方法。
  3. 本地方法棧:執行方法與虛擬機器棧相似,主要用來執行native方法。
  4. 堆區:用於儲存物件的例項。
  5. 方法區:儲存已經被虛擬機器載入的類資訊(即載入類時需要載入的資訊,包括版本、field、方法、介面等資訊)、final常量、靜態變數、編譯器即時編譯的程式碼等。

Object作為所有類的父類以他建立一個物件,將涉及哪些區域的變動呢?

Object obj = new Object();
複製程式碼

①、Object obj表示一個本地引用,儲存在虛擬機器棧的本地變數表中,表示一個reference型別資料; ②、new Object()作為例項物件資料儲存在堆中,另外還記錄了Object類的型別資訊(介面、方法、field、物件型別等)的地址,這些地址所執行的資料儲存在方法區中;

GC回收機制

既然要垃圾回收,那到底要回收的是哪些東西呢? 上文中Object類的舉例,已經有一定的苗頭了。 在方法區中的資料,是從一開始就要求被加入的,那麼回收掉他們難免會出現各種問題。而像Object這樣的類,只在一段時間內需要被使用,也就難免會成為多出來的碎片,也就成了典型的“佔著茅坑不拉屎”的了。 所以,顯而易見,我們要回收的就是這麼一類垃圾資料了,而GC回收器回收的也就是這種new出來以後沒用了的資料了。

堆區的細節劃分

JVM必備基礎知識(三)-- GC垃圾回收機制

  • 新生代:Eden 、From Space、To Space 剛建立的物件一般都被放入Eden中,Eden滿了以後,就會進行一次GC操作,刪去消亡的,把活躍的放到From中。(To Space和From Space是一個輪換的,空的那份資料就是下一輪Eden滿時要存放資料的From Space,另外一個就成了To Spcae)To Space滿了的時候就會將物件轉移到老年代。
  • 老年代 經過了多次回收,但還是堅強存活下來的物件們所在的記憶體空間。

物件存活判斷

引用計數

一個物件被引用時加一,被去除引用時減一。那麼當數值為0時,這個引用就成為了一個垃圾。 問題,如果存在迴圈引用時,就不會結束引用。

// A類持有B的引用
public class A {
    B b;
}
// B類同樣持有A的引用
public class B {
    A a;
}
// 具體使用
public class Main {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
    }
}
複製程式碼

像上述的程式碼中,引用計數就完全無法進行判定。

可達性分析

通過對一類的GC Roots連結串列遍歷,通過可達性來判定是否為垃圾。 GC Roots一般包括如下:

  • 虛擬機器棧中引用的物件
  • 方法區靜態屬性引用的物件
  • 方法區常量引用的物件
  • JNI引用的物件(Native方法)

回收演算法

複製收集

這是一個應用於新生代記憶體整理的方法。 因為新生代的Eden區是一個連續的記憶體空間,通過遍歷,把這個記憶體空間的消亡的物件刪去,活躍的物件們重新放入一個新的空白記憶體空間中。 也就是To Space和From Space的相互交換。 存在問題:記憶體摺半。

標記清理

這是一個應用於老年代記憶體整理的方法。

JVM必備基礎知識(三)-- GC垃圾回收機制

如圖所示,搜尋出活躍的物件,清除消亡物件。 存在問題:會產生空間碎片。

標記整理

這是一個應用於老年代記憶體整理的方法。

JVM必備基礎知識(三)-- GC垃圾回收機制

比標記清理演算法多一點,他需要排序。 存在問題:用效能換取空間碎片的整理。

Java引用

Java引用的分類主要也是響應了回收器的存在,一般分為以下四類:

  • 強飲用:一般為使用關鍵詞new例項化的物件,這類引用GC回收器寧可溢位,也不會回收。
  • 軟引用:有用但是不必要的物件們,只有在記憶體不足時,才會被GC回收器回收。一般使用於快取各種資源。
public class Main {
    public static void main(String[] args) {
        SoftReference<String> sr = new SoftReference<String>(new String("SoftReference"));
        System.out.println(sr.get());
    }
}
複製程式碼
  • 弱引用:GC發生時就會被回收的物件們,是一種防治oom的方法。弱引用的應用場景在我的Android工具包MVP框架中使用到。
public class Main {
    public static void main(String[] args) {
        WeakReference<String> sr = new WeakReference<String>(new String("WeakReference"));
        System.out.println(sr.get());
        // gc回收後再次檢視效果
        System.gc();                
        System.out.println(sr.get());
    }
}
複製程式碼
  • 虛引用:形同虛設的引用。

上面三種講的很清楚了,但是這個虛引用到底有什麼用呢? 它的作用在於跟蹤垃圾回收過程,在物件被收集器回收時收到一個系統通知。 當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在垃圾回收後,將這個虛引用加入引用佇列,在其關聯的虛引用出隊前,不會徹底銷燬該物件。 所以可以通過檢查引用佇列中是否有相應的虛引用來判斷物件是否已經被回收了。

以上就是我的學習成果,如果有什麼我沒有思考到的地方或是文章記憶體在錯誤,歡迎與我分享。


相關文章推薦:

JVM必備基礎知識(一)-- 類的載入機制

JVM必備基礎知識(二)-- 類載入器和雙親委派模型

相關文章