1. 程式人生 > >JVM垃圾收集器詳解

JVM垃圾收集器詳解

概述

評估一個垃圾收集(GC)演算法如何,根據如下兩個標準:

1. 吞吐量(throughput)

吞吐量是指應用程式執行緒用時佔程式總用時的比例。 例如,吞吐量99/100意味著100秒的程式執行時間應用程式執行緒運行了99秒,而在這一時間段內GC執行緒只運行了1秒。

2. 暫停時間(pause times)

暫停時間是指一個時間段內應用程式執行緒讓與GC執行緒執行而完全暫停。 例如,GC期間100毫秒的暫停時間意味著在這100毫秒期間內沒有應用程式執行緒是活動的。

單執行緒收集器

1. 新生代Serial收集器

特點:
  1. 它僅僅使用單執行緒進行垃圾回收。
  2. 它是獨佔式的垃圾回收。
  3. 進行垃圾回收時,Java應用程式中的執行緒都需要暫停(Stop-The-World)。
  4. 使用複製演算法。
  5. 適合CPU等硬體不是很好的場合。
設定引數:
-XX:+UseSerialGC 指定新生使用新生代序列收集器和老年代序列收集器,當以client模式執行時,它是預設的垃圾收集器。

2. 老年代Serial Old收集器

特點:
  1. 同Serial收集器一樣,單執行緒,獨佔式的垃圾收集器。
  2. 通常老年代垃圾回收比新生代回收要更長時間,所以可能會使應用程式停頓較長時間。
  3. 使用“標記-整理”演算法。
  4. 作為CMS收集器的後備預案,當併發收集器發生Concurrent Mode Failure時使用。
設定引數:
-XX:+UseSerialGC 新生代,老年代都使用序列收集器。
-XX:+UseParNewGC 新生代使用ParNew收集器,老年代使用序列收集器。
-XX:+UseParallelGC 新生代使用ParallelGC收集器,老年代使用序列收集器。

多執行緒收集器

1. 新生代ParNew收集器

特點:
  1. Serial收集器的多執行緒版本。
  2. 使用複製演算法。
  3. 垃圾回收時,應用程式仍會暫停,只不過由於是多執行緒回收,在多核CPU上,回收效率會高於序列收集器,反之在單核CPU,效率會不如序列收集器。
設定引數:
-XX:+UseParNewGC 新生代使用ParNew收集器,老年代使用序列收集器 。
-XX:+UseConcMarkSweepGC 新生代使用ParNew收集器,老年代使用CMS收集器 。
-XX:ParallelGCThreads=n 指回ParNew收集器工作時的執行緒數量,cpu核數小時8時,其值等於cpu數量,高於8時,可以使用公式(3+((5*CPU_count)/8))。

2. 新生代ParallelGC收集器

特點:
  1. 同ParNew收集器一樣,不同的地方在於,它非常關注系統的吞吐量(通過引數控制)。
  2. 使用複製演算法,並行的多執行緒收集器。
  3. “吞吐量優先”的收集器,適用於在後臺運算而不需要太多互動的任務。
  4. 支援自適應的GC調節策略。
設定引數:
-XX:+UseParallelGC  新生代用ParallelGC收集器,老年代使用序列收集器 。
-XX:+UseParallelOldGC  新生代用ParallelGC收集器,老年代使用ParallelOldGC收集器系統吞吐量的控制。
-XX:MaxGCPauseMillis=n(單位ms)   設定垃圾回收的最大停頓時間。
-XX:GCTimeRatio=n(n在0-100之間)  設定吞吐量的大小,假設值為n,那系統將花費不超過1/(n+1)的時間用於垃圾回收 。
-XX:+UseAdaptiveSizePolicy  開啟自適應GC策略,在這種模式下,新生代的大小,eden和survivior的比例,晉升老年代的物件年齡等引數會被自動調整,以達到堆大小,吞吐量,停頓時間之間的平衡點。

3. 老年代ParallelOldGC收集器

特點:
  1. 同新生代的ParallelGC收集器一樣,是屬於老年代的關注吞吐量的多執行緒併發收集器。
  2. 使用多執行緒和“標記-整理”演算法。
設定引數:
-XX:+UseParallelOldGC  新生代用ParallelGC收集器,老年代使用ParallelOldGC收集器,是非常關注系統吞吐量的收集器組合,適合用於對吞吐量要求較高的系統。
-XX:ParallelGCThreads=n   指回ParNew收集器工作時的執行緒數量,cpu核數小時8時,其值等於cpu數量,高於8時可以使用公式(3+((5*CPU_count)/8))。

CMS收集器(Concurrent Mark Sweep,併發標記清除)

1. 特點:

  1. 是併發回收,非獨佔式的收集器,大部分時候應用程式不會停止執行,以獲取最短回收停頓時間為目標。
  2. 針對年老代的收集器。
  3. 使用併發“標記-清除”演算法,因此回收後會有記憶體碎片,可以使引數設定進行記憶體碎片的壓縮整理。
  4. 與ParallelGC和ParallelOldGC不同,CMS主要關注系統停頓時間。

2. 主要步驟:

  1. 初始標記:為了收集應用程式的物件引用需要暫停應用程式執行緒,該階段完成後,應用程式執行緒再次啟動。
  2. 併發標記:從第一階段收集到的物件引用開始,遍歷所有其他的物件引用。
  3. 重新標記:由於第三階段是併發的,物件引用可能會發生進一步改變。因此,應用程式執行緒會再一次被暫停以更新這些變化,並且在進行實際的清理之前確保一個正確的物件引用檢視。這一階段十分重要,因為必須避免收集到仍被引用的物件。
  4. 併發清理:所有不再被應用的物件將從堆裡清除掉。
notes:初始標記與重新標記是獨佔系統資源的,不能與使用者執行緒一起執行,而其它階段則可以與使用者執行緒一起執行。

3. 引數設定:

-XX:+UseConcMarkSweepGC該標誌首先是啟用CMS收集器。預設HotSpot JVM使用的是並行收集器。老年代使用CMS收集器,新生代使用ParNew收集器。
-XX:+CMSConcurrentMTEnabled  當該標誌被啟用時,併發的CMS階段將以多執行緒執行,預設開啟。
-XX:ConcGCThreads=n  定義併發CMS過程執行時的執行緒數(早期JVM版本也叫-XX:ParallelCMSThreads)。
-XX:CMSInitiatingOccupancyFraction=n  指定老年代回收閥值,即當老年代記憶體使用率達到這個值時,會執行一次CMS回收,JDK1.6預設值為92,設定技巧: (Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100)>=Xmn。
-XX:+UseCMSInitiatingOccupancyOnly  表示只有達到閥值時才進行CMS回收。
-XX:+CMSClassUnloadingEnabled  開啟回收Perm區的記憶體,預設情況下,是需要觸發一次FullGC 。
-XX:+CMSIncrementalMode  在增量模式下,CMS收集器在併發階段,不會獨佔整個週期,而會週期性的暫停,喚醒應用執行緒。收集器把併發階段工作,劃分為片段,安排在次級(minor)回收之間執行。這對需要低延遲,執行在少量CPU伺服器上的應用很有用。
-XX:+DisableExplicitGC禁止程式中呼叫System.gc(),System.gc()的呼叫,會使用FullGC的方式回收整個堆而會忽略CMS或G1等相關收集器。
-XX:-CMSPrecleaningEnabled關閉預清理,不進行預清理,預設在併發標記後,會有一個預清理的操作,可減少停頓時間。
-XX:+UseCMSCompactAtFullCollection  開啟記憶體碎片的整理,即當CMS垃圾回收完成後,進行一次記憶體碎片整理,要注意記憶體碎片的整理並不是併發進行的,因此可能會引起程式停頓 
-XX:CMSFullGCsBeforeCompation=n  用於指定進行多少次CMS回收後, 再進行一次記憶體壓縮 
-XX:+CMSParallelRemarkEnabled  在使用UseParNewGC 的情況下,儘量減少 mark 的時間。
-XX:+ExplicitGCInvokesConcurrentJVM無論什麼時候呼叫系統GC,都執行CMS GC,而不是Full GC。
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses當有系統GC呼叫時,永久代也被包括進CMS垃圾回收的範圍內。

4. CMS缺點:

1)堆碎片
CMS收集器並沒有任何碎片整理的機制,因此有可能出現總的堆大小遠沒有耗盡,但卻因為沒有足夠連續的空間完全容納物件而不能分配物件。當這種事發生後,併發演算法不會幫上任何忙,因此會觸發Full GC。Full GC將執行吞吐量收集器的演算法,從而解決碎片問題,但卻暫停了應用程式執行緒。因此儘管CMS收集器帶來完全的併發性,但仍然有可能發生長時間的“stop-the-world”的風險。
2)無法收集浮動垃圾(Floating Garbage)
在CMS併發清理階段使用者執行緒還在執行中,伴隨著程式的執行,自然會有新的垃圾產生,而這部分垃圾產生於標記過程之後,CMS無法在當此收集中清理掉它們,只好等待下一次GC清理。這部分垃圾就稱為“浮動垃圾”。浮動垃圾可能會導致"Concurrent Mode Failure"失敗而引發另一次Full GC的產生。
3)物件分配率高
如果獲取物件例項的頻率高於收集器清除堆裡死物件的頻率,併發演算法將再次失敗。從某種程度上說,老年代將沒有足夠的可用空間來容納一個從年輕代提升過來的物件。這種情況被稱為“併發模式失敗”,並且JVM會執行堆碎片整理:觸發Full GC。此種情形經常被證實是老年代有大量不必要的物件。一個可行的辦法就是增加年輕代的堆大小,以防止年輕代短生命的物件提前進入老年代。另一個辦法就是快照執行系統的堆轉儲,並且分析過度的物件分配,找出這些物件,最終減少這些物件的申請。

G1收集器

1. 概述:

G1收集器將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留的新生代和老年代的概念,但是新生代和老年代不再是物理隔離的了,它們都是一部分Region(不需要連續)的集合。
G1跟蹤各個Region裡面垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是First Garbage名稱的由來)。這種使用Region劃分記憶體空間以及有優先順序的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的回收效率。
在G1收集器中,Region之間的物件引用以及其他收集器中的新生代和老年代之間的物件引用,虛擬機器都是使用Remembered Set來避免全域性掃描的。

2. 特點:

  1. 獨特的垃圾回收策略,屬於分代垃圾收集器。
  2. 使用分割槽演算法,不要求eden,年輕代或老年代的空間都連續。
  3. 並行性: 回收期間,可由多個執行緒同時工作,有效利用多核cpu資源。
  4. 併發性: 與應用程式可交替執行,部分工作可以和應用程式同時執行。
  5. 分代收集: 分代收集器,同時兼顧年輕代和老年代。
  6. 空間整合: G1從整體上看是基於“標記-整理”演算法實現的收集器,從區域性(兩個Region之間)上看是基於“複製”演算法實現的;這意味著G1執行期間不會產生記憶體空間碎片。
  7. 可預見性: G1可選取部分割槽域進行回收,可以縮小回收範圍,減少全域性停頓。

3. 主要步驟:

  1. 初始標記(Initial Marking)
  2. 併發標記(Concurrent Marking)
  3. 最終標記(Final Marking)
  4. 篩選回收(Live Data Counting and Evacuation)

4. 設定引數:

-XX:+UseG1GC  開啟G1收集器開關。
-XX:MaxGCPauseMillis=n  指定目標的最大停頓時間,任何一次停頓時間超過這個值,G1就會嘗試調整新生代和老年代的比例,調整堆大小,調整晉升年齡。
-XX:ParallelGCThreads=n  用於設定並行回收時,GC的工作執行緒數量。
-XX:InitiatingHeapOccpancyPercent=n  指定整個堆的使用率達到多少時,執行一次併發標記週期,預設45, 過大會導致併發標記週期遲遲不能啟動,增加FullGC的可能,過小會導致GC頻繁,會導致應用程式效能有所下降。