JVM 學習筆記(五)

前言:

  前面的文件介紹了JVM的內存模型以及各個區域存放了那些內容,本編文章將介紹JVM中的垃圾回收Garbage Collector,和你們一塊兒探討一下。算法

如何肯定一個對象是垃圾:

  這裏介紹兩種方法:服務器

  • 引用計數法

  對於某個對象而言,只要應用程序中持有該對象的引用,就說明該對象不是垃圾,若是一個對象沒有任何指針對其引用,它就是垃圾。
  • 可達性分析

  經過GC Root的對象,開始向下尋找,看某個對象是否可達。能做爲GC Root:類加載器、Thread、虛擬機棧的本地變量表、static成員、常量引用、本地方法
棧的變量等。

垃圾回收算法:

  已經可以肯定一個對象爲垃圾以後,接下來要考慮的就是回收,怎麼回收呢?得要有對應的算法,下面聊聊常見的垃圾回收算法。
  • 標記-清除(Mark-Sweep)

  1. 標記

  找出內存中須要回收的對象,而且把它們標記出來。此時堆中全部的對象都會被掃描一遍,從而才能肯定須要回收的對象,比較耗時。多線程

如圖:綠色的區域表示當前存活的對象,灰色表示垃圾對象,白色表示沒有用到的內存碎片。併發

      2. 清除

  清除掉被標記須要回收的對象,釋放出對應的內存空間。

 

有如下缺點:佈局

標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另
一次垃圾收集動做。
(1)標記和清除兩個過程都比較耗時,效率不高
(2)會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
  • 複製(Copying)

  將內存劃分爲兩塊相等的區域,每次只使用其中一塊,如圖所示:

 

 

  當其中一塊內存使用完了,就將還存活的對象複製到另一塊上面,而後把已經使用過的內存空間一次清除掉。性能

下圖的清理事後的內存模型:線程

 

 缺點:翻譯

  由於這種方法保留的兩個大小同樣的內存區域,而同一時刻只會用到其中的一個,因此該方法內存的空間利用率比較低。設計

  • 標記-整理(Mark-Compact)

  標記過程仍然與"標記-清除"算法同樣,可是後續步驟不是直接對可回收對象進行清理,而是讓全部存活
的對象都向一端移動,而後直接清理掉端邊界之外的內存。 
  如圖是標記階段,該階段會將全部的垃圾作上標記。

 

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

 

 

 

分代收集算法:

  既然上面介紹了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秒,使用併發收集器

相關文章
相關標籤/搜索