JVM 學習筆記(五)

會說話的丶貓發表於2020-07-11

前言:

  前面的檔案介紹了JVM的記憶體模型以及各個區域存放了那些內容,本編文章將介紹JVM中的垃圾回收Garbage Collector,和大家一起探討一下。

如何確定一個物件是垃圾:

  這裡介紹兩種方法:

  • 引用計數法

  對於某個物件而言,只要應用程式中持有該物件的引用,就說明該物件不是垃圾,如果一個物件沒有任何指標對其引用,它就是垃圾。
  • 可達性分析

  通過GC Root的物件,開始向下尋找,看某個物件是否可達。能作為GC Root:類載入器、Thread、虛擬機器棧的本地變數表、static成員、常量引用、本地方法
棧的變數等。

垃圾回收演算法:

  已經能夠確定一個物件為垃圾之後,接下來要考慮的就是回收,怎麼回收呢?得要有對應的演算法,下面聊聊常見的垃圾回收演算法。
  • 標記-清除(Mark-Sweep)

  1. 標記

  找出記憶體中需要回收的物件,並且把它們標記出來。此時堆中所有的物件都會被掃描一遍,從而才能確定需要回收的物件,比較耗時。

如圖:綠色的區域表示當前存活的物件,灰色表示垃圾物件,白色表示沒有用到的記憶體碎片。

      2. 清除

  清除掉被標記需要回收的物件,釋放出對應的記憶體空間。

 

有以下缺點:

標記清除之後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另
一次垃圾收集動作。
(1)標記和清除兩個過程都比較耗時,效率不高
(2)會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
  • 複製(Copying)

  將記憶體劃分為兩塊相等的區域,每次只使用其中一塊,如圖所示:

 

 

  當其中一塊記憶體使用完了,就將還存活的物件複製到另外一塊上面,然後把已經使用過的記憶體空間一次清除掉。

下圖的清理過後的記憶體模型:

 

 缺點:

  因為這種方法保留的兩個大小一樣的記憶體區域,而同一時刻只會用到其中的一個,所以該方法記憶體的空間利用率比較低。

  • 標記-整理(Mark-Compact)

  標記過程仍然與"標記-清除"演算法一樣,但是後續步驟不是直接對可回收物件進行清理,而是讓所有存活
的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。 
  如圖是標記階段,該階段會將所有的垃圾做上標記。

 

   下圖是整理階段,該階段會將被標記的區域清除,並把存活的物件往一端移動,這樣記憶體區域就會連續化,不會有空間碎片。

 

 

 

分代收集演算法:

  既然上面介紹了3中垃圾收集演算法,那麼在堆記憶體中到底用哪一個呢?
Young區(俗稱新生代):複製演算法(物件在被分配之後,可能生命週期比較短,Young區複製效率比較高) 
Old區(俗稱老年代):標記清除或標記整理(Old區物件存活時間比較長,複製來複制去沒必要,不如做個標記再清理) 

垃圾收集器的介紹:

  如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。
先上一張Young區和Old區垃圾收集器的適用圖:

 

 下面來介紹這幾種垃圾收集器:

1.Serial收集器

  Serial收集器是最基本、發展歷史最悠久的收集器,曾經(在JDK1.3.1之前)是虛擬機器新生代收集的唯一選擇。它是一種單執行緒收集器,不僅僅意味著它只會使用一個CPU或者一條收集執行緒去完成垃圾收集工作,更重要的是其在進行垃圾收集的時候需要暫停其他執行緒。
  下面簡單總結一下Serial收集器:

優點:簡單高效,擁有很高的單執行緒收集效率
缺點:收集過程需要暫停所有執行緒
演算法:複製演算法
適用範圍:新生代
應用:Client模式下的預設新生代收集器

  下圖是該模式下的應用執行緒狀態圖:

 2. ParNew收集器

  簡單理解為是Serial收集器的多執行緒版本。

簡單總結一下該收集器:

優點:在多CPU時,比Serial效率高。
缺點:收集過程暫停所有應用程式執行緒,單CPU時比Serial效率差。
演算法:複製演算法
適用範圍:新生代
應用:執行在Server模式下的虛擬機器中首選的新生代收集器

3. Parallel Scavenge收集器

  Parallel Scavenge收集器是一個新生代收集器,它也是使用複製演算法的收集器,又是並行的多執行緒收集器,看上去和ParNew一樣,但是Parallel Scanvenge更關注系統的吞吐量 。

這裡解釋一下什麼是吞吐量:

吞吐量=執行使用者程式碼的時間/(執行使用者程式碼的時間+垃圾收集時間)
比如虛擬機器總共執行了100分鐘,垃圾收集時間用了1分鐘,吞吐量=(100-1)/100=99%。
若吞吐量越大,意味著垃圾收集的時間越短,則使用者程式碼可以充分利用CPU資源,儘快完成程式的運算任務。

4. Serial Old收集器

  Serial Old收集器是Serial收集器的老年代版本,也是一個單執行緒收集器,不同的是採用"標記-整理演算法",執行過程和Serial收集器一樣。

 下圖是該模式下的應用執行緒狀態圖:

 

 5. Parallel Old收集器

  Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多執行緒和"標記-整理演算法"進行垃圾回收。

6. CMS收集器

  CMS(Concurrent Mark Sweep)收集器是一種以獲取 最短回收停頓時間 為目標的收集器。
採用的是"標記-清除演算法",整個過程分為4步 
 
(1) 初始標記     CMS initial mark         標記GC Roots能關聯到的物件        Stop The World--->速度很快 
(2) 併發標記     CMS concurrent mark     進行GC Roots Tracing 
(3) 重新標記     CMS remark         修改併發標記因使用者程式變動的內容     Stop The World 
(4) 併發清除      CMS concurrent sweep
 
  由於整個過程中,併發標記和併發清除,收集器執行緒可以與使用者執行緒一起工作,所以總體上來說,CMS收集器的記憶體回收過程是與使用者執行緒一起併發地執行的。
簡單總結一下優缺點:
優點:併發收集,低停頓。
缺點:產生大量空間碎片,併發階段會降低吞吐量。

 

 7. G1收集器

  G1 (Garbage-First)是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量效能特徵. 在Oracle JDK 7 update 4 及以上版本中得到完全支援, 專為以下應用程式設計:

  • 可以像CMS收集器一樣,GC操作與應用的執行緒一起併發執行
  • 緊湊的空閒記憶體區間且沒有很長的GC停頓時間.
  • 需要可預測的GC暫停耗時.
  • 不想犧牲太多吞吐量效能.
  • 啟動後不需要請求更大的Java堆.

  G1的長期目標是取代CMS(Concurrent Mark-Sweep Collector, 併發標記-清除). 因為特性的不同使G1成為比CMS更好的解決方案. 一個區別是,G1是一款壓縮型的收集器.G1通過有效的壓縮完全避免了對細微空閒記憶體空間的分配,不用依賴於regions,這不僅大大簡化了收集器,而且還消除了潛在的記憶體碎片問題。除壓縮以外,G1的垃圾收集停頓也比CMS容易估計,也允許使用者自定義所希望的停頓引數(pause targets)

歸納總結一下G1收集器的特點:

1.並行與併發

2.分代收集(仍然保留了分代的概念)

3.空間整合(整體上屬於“標記-整理”演算法,不會導致空間碎片) 

4.可預測的停頓(比CMS更先進的地方在於能讓使用者明確指定一個長度為M毫秒的時間片段內,消耗在垃圾收集 上的時間不得超過N毫秒)。

  使用G1收集器時,Java堆的記憶體佈局與就與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。
  工作過程可以分為如下幾步:

初始標記(Initial Marking) 標記一下GC Roots能夠關聯的物件,並且修改TAMS的值,需要暫 停使用者執行緒
併發標記(Concurrent Marking) 從GC Roots進行可達性分析,找出存活的物件,與使用者執行緒併發 執行
最終標記(Final Marking) 修正在併發標記階段因為使用者程式的併發執行導致變動的資料,需 暫停使用者執行緒
篩選回收(Live Data Counting and Evacuation) 對各個Region的回收價值和成本進行排序,根據 使用者所期望的GC停頓時間制定回收計劃

 

 

垃圾收集器分類:

序列收集器->Serial和Serial Old

  只能有一個垃圾回收執行緒執行,使用者執行緒暫停。 適用於記憶體比較小的嵌入式裝置 。

並行收集器[吞吐量優先]->Parallel Scanvenge、Parallel Old

  多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。 適用於科學計算、後臺處理等若互動場景 。

併發收集器[停頓時間優先]->CMS、G1

  使用者執行緒和垃圾收集執行緒同時執行(但並不一定是並行的,可能是交替執行的),垃圾收集執行緒在執行的時候不會停頓使用者執行緒的執行。 適用於相對時間有要求的場景,比如Web 。 

理解吞吐量和停頓時間:

  停頓時間->垃圾收集器 進行 垃圾回收終端應用執行響應的時間。
  吞吐量->執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間) 。
  停頓時間越短就越適合需要和使用者互動的程式,良好的響應速度能提升使用者體驗;高吞吐量則可以高效地利用CPU時間,儘快完成程式的運算任務,主要適合在後臺運算而不需要太多互動的任務。 
 

如何選擇合適的垃圾收集器:

  首先我們瞭解一下官網是如何建議的:

 

   簡單翻譯一下就是:

  1.優先調整堆的大小讓伺服器自己來選擇
  2.如果記憶體小於100M,使用序列收集器
  3.如果是單核,並且沒有停頓時間要求,使用序列或JVM自己選
  4.如果允許停頓時間超過1秒,選擇並行或JVM自己選
  5.如果響應時間最重要,並且不能超過1秒,使用併發收集器

相關文章