小夥子,你真的清楚 JVM GC ?

  • 2019 年 10 月 3 日
  • 筆記

 序


正文


 如何確定垃圾?

前面已經提到 JVM 可以採用 引用計數法 與 可達性分析演算法 來確定需要回收的垃圾,我們來具體看一下這兩種演算法:

  • 引用計數法

該方法實現為:給每個對象添加一個引用計數器,每當有一個地方引用它時,引用計數值就+1,當引用失效時,引用計數值就-1,任何時刻引用計數值為0的對象就可以被回收,當一個對象被垃圾收集器收集時,被它引用的對象引用計數值就-1,所以在這種方法中一個對象被垃圾收集會導致後續其他對象的垃圾收集行動。

優點:簡單、高效;

缺點:當兩個對象相互引用的時候就無法回收,導致記憶體泄漏。

  • 可達性分析法

為了解決對象間相互引用問題,Java 採用了可達性分析法,基本實現思路為:通過一系列 “GC Roots” 對象作為起點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到 “GC Roots” 沒有任何引用鏈相連時,則稱該對象是不可達的,此時,該對象還處於緩刑階段,要真正宣判一個對象為可回收對象,至少要經歷兩次標記過程。

哪些對象可以作為 “GC Roots” ?

1、通過System Class Loader或者Boot Class Loader載入的class對象
2、處於激活狀態的執行緒
3、棧中的對象
4、JNI棧中的對象
5、JNI中的全局對象
6、正在被用於同步的各種鎖對象
7、JVM自身持有的對象,比如系統類載入器等

 垃圾回收演算法

通過上面面試者的回答,我們已經知道垃圾收集演算法主要包括:複製演算法、標記清除演算法、標記整理演算法、分代回收演算法,我們來具體看一下:

  • 複製演算法

該方法實現為:將記憶體分為大小相等的兩塊,每次只使用其中一塊,當這一塊記憶體滿了後將存活的對象複製到另一塊上去,把已使用的記憶體清掉。如圖所示:

優點:簡單、高效、不會產生記憶體碎片;

缺點:可用記憶體減少為原來的一半,造成記憶體浪費。

  • 標記清除演算法

該方法實現分為兩個階段,標註和清除,標記階段找到所有可訪問的對象,做個標記 ;清除階段遍歷堆,把未被標記的對象回收。如圖所示:

缺點:碎片化嚴重。

  • 標記整理演算法

該方法不直接對可回收對象進行清理,而是讓所有可用的對象都向一端移動,然後直接清理掉邊界外的對象,解決了標記清除演算法帶來的碎片化問題。如圖所示:

  • 分代回收演算法

分代回收演算法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的生命周期不同,將記憶體劃分為不同的區域,一般情況下將 GC 堆劃分為新生代和老年代;老年代的特點是:對象生命周期較長,每次垃圾回收時只有少量對象需要被回收;新生代的特點是:對象大部分朝生夕死,生命周期短,每次垃圾回收時都有大量對象需要被回收;因此,可以根據不同區域選擇不同的演算法,使垃圾回收更加合理、高效,如:新生代採用效率較高的複製演算法,老年代採用不會產生記憶體碎片,也不會發生記憶體浪費的標記整理演算法。

  垃圾收集器

常用的垃圾收集器都有哪些呢?我們來具體看一下:

  • Serial 垃圾收集器

Serial 曾經是 JDK1.3.1 之前新生代唯一的垃圾收集器;Serial 是一個單執行緒的收集器,在進行垃圾收集的同時,必須暫停其他所有的工作執行緒,直到垃圾收集結束;但同時 Serial 也是簡單高效的,對於限定單個 CPU 環境來說,沒有執行緒交互的開銷,可以獲得最高的單執行緒垃圾收集效率。

  • ParNew 垃圾收集器

ParNew 是 Serial 多執行緒版,也使用複製演算法,除了使用多執行緒進行垃圾收集之外,其餘的行為和 Serial 收集器完全一樣。

  • Parallel Scavenge 收集器

Parallel Scavenge 是一個新生代垃圾收集器,同樣使用複製演算法,也是一個多執行緒的垃圾收集器,它重點關注的是程式達到一個可控制的吞吐量(吞吐量=運行用戶程式碼時間/(運行用戶程式碼時間+垃圾收集時間)),高吞吐量可以最高效率地利用 CPU 時間,儘快地完成程式的運算任務,主要適用於在後台運算而不需要太多交互的任務。

  • Serial Old 收集器

Serial Old 是 Serial 垃圾收集器老年代版本,它是個單執行緒的,使用標記整理演算法的收集器。

  • Parallel Old 收集器

Parallel Old 收集器是 Parallel Scavenge 的老年代版本,它是多執行緒的,使用標記整理演算法,在 JDK1.6 開始提供使用。

  • CMS 收集器

CMS 是一種老年代垃圾收集器,其主要目標是獲取最短垃圾回收停頓時間,和其它老年代使用標記整理演算法不同,它使用多執行緒的標記清除演算法;其運作過程比較複雜,整個過程分為6個步驟,包括:初始標記(CMS initial mark)、並發標記(CMS concurrent mark)、並發預清理(CMS-concurrent-preclean)、重新標記(CMS remark)、並發清除(CMS concurrent sweep)、並發重置(CMS-concurrent-reset),其中初始標記、重新標記這兩個步驟仍然需要暫停其它工作執行緒,初始標記僅僅只是標記一下 “GC Roots” 能直接關聯到的對象,速度很快,並發標記階段就是進行 “GC Roots” 追蹤的過程,而重新標記階段則是為了修正並發標記期間,因用戶程式繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短。

  • G1 收集器

G1 是 JVM 垃圾收集器理論進一步發展的產物,相比與 CMS 收集器,G1 基於標記整理演算法,不會產生記憶體碎片;其次,還可以非常精確控制停頓時間,在不犧牲吞吐量前提下,實現低停頓垃圾回收;G1 收集器避免全區域垃圾收集,它把堆記憶體劃分為大小固定的幾個獨立區域,並且跟蹤這些區域的垃圾收集進度,同時在後台維護一個優先順序列表,每次根據所允許的收集時間,優先回收垃圾最多的區域;區域劃分和優先順序區域回收機制,確保 G1 收集器可以在有限時間獲得最高的垃圾收集效率。