1. 程式人生 > >JVM - 垃圾收集器(一)

JVM - 垃圾收集器(一)

HotSpot虛擬機器垃圾收集器的種類以及作用空間(連線表示兩個收集器可以搭配使用)

Serial 收集器:該收集器單執行緒工作,在gc時暫停使用者其他所有的工作執行緒即“Stop The World”,新生代採用複製演算法,老年代採用 標記-整理 演算法,是虛擬機器執行在Client模式下預設的新生代收集器,簡單高效。

Serial/Serial Old 收集器執行示意圖

ParNew 收集器:該收集器是Serial 收集器的多執行緒版本,也是除了Serial收集器之外可以與CMS收集器配合工作的收集器。是執行在 Server模式下 

的虛擬機器中首選的新生代收集器。ParNew 收集器也是使用 -XX:UseConcMarkSweepGC 選項後的預設新生代收集器,也可以使用 -XX:UseParNewGC 選項來強制指定它。

ParNew/Serial Old收集器執行示意圖

Parallel Scavenge 收集器:該收集器是一個新生代收集器,它也是採用 複製演算法 的收集器,又是 並行 的多執行緒收集器。從兩個點了解該收集器即可:1. 吞吐量;2. 自適應策略。

吞吐量:Parallel Scavenge 收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是儘可能地縮短垃圾收集時使用者執行緒的停頓時間,而Parallel Scavenge 收集器的目標則是達到一個可控制的吞吐量。所謂吞吐量就是CPU用於執行使用者程式碼的時間與CPU總消耗時間的比值,即 吞吐量 = 執行使用者程式碼時間/(執行使用者程式碼時間 + 垃圾收集時間)。高吞吐量可以高效的利用CPU時間。

Parallel Scavenge 收集器提供兩個引數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis 引數以及直接設定吞吐量大小的 -XX:GCTimeRatio 引數。MaxGCPauseMillis引數允許的值是一個大於0的毫秒數,收集器儘可能地保證記憶體回收花費的時間不超過設定的值。GCTimeRatio 引數的值是一個大於0且小於100的值,也就是垃圾收集時間佔總時間的比率。

自適應策略:Parallel Scavenge 收集器通過開啟 -XX:+UseAdaptiveSizePolicy 引數不需要手工指定新生代的大小(-Xmm)、Eden與Survivor區的比例(-XX:SUrvivorRatio)、晉升老年代物件大小(-XX:PretenureSizeThreshold)等細節引數,虛擬機器會根據當前系統的執行情況收集效能監控資訊,動態調整這些引數以提供最合適的停頓時間或者最大吞吐量這種調節反射光輝成為GC自適應的調節策略。如果對虛擬機器收集器運作原理不瞭解,使用該引數是一個比較好的選擇。

Serial Old 收集器:是Serial收集器的老年代版本,同樣是一個單執行緒收集器,使用標記-整理演算法,給Client模式下執行的虛擬機器使用。如果在Server模式下使用該收集器那麼它有兩種用途:1. 在jdk1.5以及之前的版本中搭配Parallel Scavenge 收集器使用;2. 作為 CMS收集器的後備預案,在併發收集發生 Concurrent Mode Failure 時使用。

 

Serial/Serial Old 收集器執行示意圖

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

Parallel Scavenge / Parallel Old 收集器執行示意圖

CMS 收集器:CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。基於標記-清除演算法實現。整個運作過程分為四步。

初始標記(CMS initial mark)

併發標記(CMS concurrent mark)

重新標記(CMS remark)

併發清除(CMS concurrent sweep)

其中,初始標記和重新標記這兩個步驟仍然需要 “Stop The World”,初始標記僅僅只是標記一下 GC Roots 能直接關聯到的物件,速度很快,併發標記階段就是 進行 GC Roots Tracing 的過程,而重新標記階段則是為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄,這個階段的停段時間一般比初始標記階段稍長一點,但遠比並發標記時間短。由於這整個過程中耗時最長的併發標記和併發清除過程收集器的執行緒可以與使用者執行緒一起工作,所以從整體上來說,CMS收集器的記憶體回收過程是與使用者執行緒一起併發執行的。(圖片來自百度)

 CMS 收集器的優點:併發收集、低停頓。

CMS 收集器的缺點:

  • CMS 收集器對CPU資源非常敏感。其實,面向併發設計的程式都對CPU資源比較敏感。在併發階段,它雖然不會導致使用者執行緒停頓,但是因為佔用了一部分執行緒(或者說CPU資源)而導致應用程式變慢,總吞吐量降低。CMS預設啟動的回收執行緒數是(CPU數量+3)/4,也就是當CPU在4個以上時,併發回收時垃圾縣暱稱不少於25%的CPU資源,並且隨著CPU的數量增加而下降。但是當CPU不足4個(譬如2個)時,CMS對使用者的影響可能變得很大,如果本來CPU負載比較大,還分出一半的運算能力去執行收集執行緒,就可能導致使用者程式的執行速度忽然降低了50%。為了應付這種情況,虛擬機器提供了一種“增量式併發收集器”(i-CMS),但是實踐證明效果一般,已經棄用。 
  • CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure”失敗而導致另一次 Full GC 的產生。由於CMS併發清理階段使用者執行緒還在執行著,伴隨程式的執行自然就還會有新的垃圾產生,這一部分垃圾出現在標記過程之後,CMS無法在當次收集中處理掉它們,只好留待下次GC時在清理掉。這一部分垃圾就成為“浮動垃圾”。也是由於在垃圾收集階段使用者執行緒還需要執行,那也就還需要預留足夠的空間給使用者執行緒使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎塞滿了在進行垃圾收集,需要預留一部分空間提供併發收集時的程式運作使用。在JDK1.5的預設設定下,CMS收集器當老年代使用了 68% 的空間後就會被啟用,這是一個偏保守的設定,如果應用中老年代增長的不是太快,可以適當調高參數 -XX:CMSInitiatingOccupancyFraction 的值來提高觸發百分比,以便降低記憶體回收的次數從而獲取更好的效能,在JDK1.6中,CMS收集器的啟動閾值已經提升至92%。要是CMS執行期間預留的記憶體無法滿足程式的需要時,就會出現一次“Concurrent Mode Failure”失敗,這是虛擬機器將啟動後備預案,臨時啟用 Serial Old 收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說引數 -XX:CMSInitiatingOccupancyFraction 設定的太高很容易導致大量“Concurrent Mode Failure”失敗,效能反而降低。
  • CMS因為採用標記-清除演算法實現,會產生記憶體空間碎片。CMS是一款基於 標記-清除 演算法實現的收集器,收集結束時會產生大量記憶體空間碎片。空間碎片過多時,將會給大物件分配帶來很大的麻煩,往往會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續記憶體空間來分配當前物件,不得不提前觸發一次 Full GC。為了解決這個問題虛擬機器提供了一個 -XX:+UseCMSCompactAtFullColletion 開關引數(預設開啟),用於在CMS收集器頂不住要進行Full GC時開啟記憶體碎片的合併整理過程,記憶體整理的過程是無法併發的,空間碎片沒有了,但停頓時間變長了。虛擬機器還提供了另外一個引數 -XX:CMSFullFCsBeforeCompaction ,這個引數的作用是設定執行多少次不壓縮的 Full GC後,跟著來一次帶壓縮的(預設值0,表示每次進入 Full GC時都進行碎片整理)