JVM 學習筆記(五)

前言:

  前面的文件介紹了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秒,使用並發收集器

Tags: