深入理解Java虛擬機第三章讀書筆記:HotSpot虛擬機中的各個垃圾收集器

1.前言

垃圾收集算法是內存回收的方法論,那麼垃圾收集器就是垃圾回收的具體實現。書中提到的HotSpot虛擬機所實現的所有垃圾收集器如圖所示,有連線的兩個垃圾收集器表示這兩種垃圾收集器可以搭配使用,垃圾收集器所處的區域表明它適用的區域,圖中橫線上方是新生代區域,下方是老年代區域

在垃圾收集器的上下文語境中,併發和並行的解釋如下:

  • 並行:多條垃圾收集線程同時工作,但是用戶線程還處在停滯等待狀態中
  • 併發:用戶線程與垃圾收集線程同時執行(不一定是並行的,也有可能是交替執行),用戶程序在繼續執行,而垃圾收集線程運行於另外一個CPU上

2.各種垃圾收集器

2.1 Serial收集器(用於新生代,使用複製算法)

serial收集器是歷史最悠久,最基本的垃圾收集器。

它是一個單線程的收集器,這個單線程並不僅僅只是說明它只用一個CPU或者一個收集線程去進行垃圾收集,更中重要的是它在進行垃圾收集工作的時候要暫停其他所有的用戶進程,直至它工作結束,也就是常說的“Stop The World”。

缺點:

  • 收集線程工作時,要暫停其他的所有進程。就好比說電腦用了一個小時,然後要暫停個五分鐘讓serial收集器去工作,這樣的用戶體驗是很不好的。

優點:

  • 簡單高效(與其他單線程的收集器相比)。在單個CPU的環境來說,serial收集器由於沒有線程交互的額外開銷,專心的做垃圾收集工作自然可以獲得最高的單線程收集效益。
  • Serial收集器對於運行在Client模式下的虛擬機來說任然是一個很好的選擇。並且它是虛擬機運行在Client模式下的默認新生代收集器。

2.2 ParNew收集器(用於新生代,使用複製算法)

ParNew收集器就是Serial收集器的多線程版本。

除了是使用多線程進行垃圾收集以外,其餘的包括控制參數、收集算法、STW、對象分配規則、回收策略等都與Serial收集器一模一樣。

ParNew收集器是運行在Server模式下的虛擬機中首選的新生代收集器 ,有一個與性能無關但是很重要的原因是只有它才能跟CMS收集器進行配合工作。

ParNew收集器在單CPU的情況下不會有比Serial收集器更好的效果,甚至因爲存在多線程交互而產生的開銷,在兩個CPU的環境中都不能百分之百的保證性能可以超過Serial收集器。

2.3 Parallel Scavenge收集器(用於新生代,使用複製算法)

Parallel Scavenge收集器又被稱爲“吞吐優先”收集器。

Parallel Scavenge收集器的特點是它與其他收集器的關注點不同,它關注的是吞吐量,CMS等收集器的關注點是儘量縮短垃圾收集時用戶線程的停頓等待時間

吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。例如虛擬機共執行了100分鐘,垃圾收集花掉了1分鐘,那麼吞吐量就是99%。

停頓時間越短就越適合與用戶交互的程序,良好的響應速度能提高用戶的體驗效果。而高吞吐量則可以高效率地利用CPU時間,儘快完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務。

2.4 Serial Old收集器(用於老年代,使用標記-整理算法)

Serial Old收集器是Serial收集器的老年版本,也是有一個單線程的垃圾收集器。

他存在的主要作用也是給運行在Client模式下的虛擬機使用。

在Server模式下使用,它還有兩大用途:

  • 一種是在JDK1.5以及之前的版本中與Parallel Scavenge收集器搭配使用
  • 另一種就是作爲CMS收集器的後被預案,在併發收集中出現Concurrent Mode Failure時使用

2.5 Parallel Old收集器(用於老年代,使用標記-整理算法)

Parallel Old收集器是Parallel Scavenge收集器的老年代版本。

在注重吞吐量以及CPU資源敏感的場景下,都可以優先考慮使用Parallel Scavenge + Parallel Old收集器

2.6 CMS收集器(用於老年代,使用標記-清除算法)

CMS收集是一種以回收停頓時間最短爲目標的收集器。

目前很大一部分的Java應用都是集中在互聯網站和B/S系統的服務器上,這類應用尤其重視服務的響應速度,希望系統的停頓時間最短,以便給用戶帶來最好的用戶體驗。CMS收集器就非常符合這類應用的需求。

CMS收集器的運作過程分爲4個步驟:

  1. 初始標記-------->需要Stop The World,初始標記僅僅是標記一下GC Roots能直接關聯到的對象,速度很快
  2. 併發標記-------->進行GC Roots Tracing的過程,這個階段GC線程與用戶線程併發執行,由前階段已經標記過的對象出發,標記那些與Gc Roots間接關聯的對象,所有GC Roots可達的對象都會在本階段被標記完成
  3. 重新標記-------->在併發標記階段可能有些對象從新生代升到了老年代,或者有些對象被進行了修改,那麼通過這個階段來重新標記這些對象,此階段需要Stop The World,並且這個階段是多線程的
  4. 併發清除-------->用戶進程被重新激活,GC線程將無用的對象進行清除

CMS收集器的3個缺點:

  • CMS收集器對CPU資源非常敏感。在併發階段他雖然不會導致用戶線程停頓,但是因爲它佔用了一部分的CPU資源所以會導致應用程序變慢。CMS默認開啓的線程數是(CPU數量+3)/ 4,也就是當CPU數量在4個以上時,併發回收時垃圾收集線程會佔用不少於25%的CPU資源,並且隨着CPU的數量增加而下降。但是當CPU不足4個,比如兩個的話,就需要分出一半的資源去執行收集器線程。
  • CMS沒有辦法清理“浮動垃圾”(因爲CMS在併發清除階段時,用戶線程還在進行,伴隨用戶線程運行的同時,還會產生新的垃圾,而CMS無法在當次處理時對他們進行清理,這些垃圾就叫做浮動垃圾),可能出現Concurrent Mode Failure失敗而導致另一次Full GC的執行。CMS無法在當次的垃圾清理中對浮動垃圾進行清除,所以只能留待下一次GC時再進行清理。因爲垃圾清理的同時用戶線程也在執行,所以還需要留出足夠的內存空間來給用戶線程使用,所以CMS收集器沒有辦法像其他收集器一樣等到老年代空間滿了才進行清理,必須要預留一部分空間提供給併發執行的用戶線程使用。在JDK1.6中,只要老年代使用了92%,那麼CMS就會啓動。要是在CMS運行期間預留的內存無法滿足用戶線程的需求,那麼就會出現一次Concurrent Mode Failure失敗,這時虛擬機就會啓動備案:即啓用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓的時間就很長了。
  • CMS使用的是“標記-清除”算法,所以會產生空間碎片,可能導致給大對象分配內存空間時,連續內存不足,從而導致提前觸發一次Full GC,爲了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關參數(默認打開),用於在CMS收集器頂不住要進行Full GC的時候開啓內存碎片的合併整理過程,內存整理的過程是無法併發的,所以要停掉用戶線程,從而使得用戶線程停頓時間加長,背離了CMS收集器的初衷。

2.7 G1收集器(跨代收集器,即可用於新生代也可以用於老年代)

G1(Garbage First)收集器是當今收集器技術發展最前沿的成果之一。

G1收集器是一款面向服務端應用的垃圾收集器。HotSpot團隊的目標是讓G1收集器日後能夠代替CMS收集器。

與其他收集器相比,G1收集器具備以下的特點:

  1. 併發與並行:G1收集器可以有效的利用多CPU資源,多核環境下的硬件優勢。當其他收集器執行GC線程的時候,會Stop The World,但是G1收集器任然可以使用併發來使用戶線程繼續執行
  2. 分代收集:雖然G1收集器可以不需要其他的收集器配合,就可以獨立管理整個GC堆,但是在不同的區域,它任然使用了不同的收集策略以達到更好的GC效果
  3. 內存整理:不同於CMS的標記-清除算法,G1使用了標記-整理算法以及複製收集算法,從而避免了空間碎片的問題。收集後能夠保證堆內存的規整。這種特性有利於程序的長時間運行,分配大對象時不會因爲無法得到足夠的內存空間而提前觸發下一次GC
  4. 可預測的停頓,這是G1相對於CMS的另一大優勢。降低停頓時間是G1和CMS共同的關注點。G1除了追求低停頓以外還可以建立可預測的停頓時間模型,能讓使用者明確指定一個長度爲M毫秒的時間片段內,消耗在垃圾收集上的時間不超過N毫秒

前面介紹的幾款收集器都是隻能用於新生代或者只能用於老年代,而G1既能夠收集新生代又可以收集老年代是因爲它把堆內存劃分成多個大小相等的獨立區域Region,新生代和老年代不再是物理隔離了,他們是多個Region的集合(Region可以物理上不連續)。

G1之所以可以建立可預測的停頓模型,是因爲它可以有計劃的避免對整個Java堆進行全區域垃圾收集,G1跟蹤各個Reigon裏面的垃圾堆積的價值大小(回收所獲的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,有限回收價值最大的Region。這種使用Region劃分內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效益。

G1收集器中,Region之間的對象引用以及其他收集器的新生代和老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描的。G1中的每一個Region都會有一個Remembered Set來與之對應。虛擬機發現程序在對reference類型的數據進行寫操作時,會產生一個Write Barrier暫時中斷寫操作,檢查reference引用的對象是都處於同一個Region,如果不是,那麼就通過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set中。當進行內存回收時,在GC根節點的枚舉範圍內加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

G1收集器的運作大致可以分爲以下四個階段:

  1. 初始標記:標記與GC Roots直接關聯的對象(需要線程停頓,但是耗時很短)
  2. 併發標記:從GC Roots開始對堆中對象進行可達性分析,找出所有與GC Roots相關聯的存活對象(可以與用戶線程併發執行,耗時較長)
  3. 最終標記:爲了修正在併發標記期間因爲用戶線程運行而導致標記產生改變的對象(需要停頓用戶線程,但是可以並行執行)
  4. 篩選回收:首先對各個Region的回收價值進行排序,根據用戶所期望的GC停頓時間來制定回收計劃,這個階段其實是可以與用戶線程併發執行的,但是用戶線程停頓的話會得到更高的收集效率

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章