JDK8垃圾回收調優指南--(8)CMS

原文:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide--Concurrent Mark Sweep (CMS) Collector

併發標記清除收集器(CMS)是爲了那些要求更短GC停頓的應用設計的,它能夠在應用運行期間共享處理器資源。通常這種應用的“長生命週期”數據集相對較大(大型老年代)並且運行在具有兩個或更多處理器的機器上更易於發揮這種收集器的優勢。但是,對於任何要求低GC停頓的應用,都應該考慮使用此收集器。CMS收集器的啓用命令行選項:'-XX:+UseConcMarkSweepGC'。

與其它收集器類似,CMS也是分代收集;因此'minor collection'和'major collection'都會發生。CMS嘗試在應用運行期間使用特定的一些回收線程併發地跟蹤可訪問的對象,從而降低由於'major collection'導致的GC停頓。在每個'major collection'週期中,CMS會在開始和回收中期暫停所有應用線程一小段時間。第二次暫停往往是兩次暫停中較長的一次。在兩次GC停頓期間多個線程會執行回收工作。回收週期的其餘部分(包括對存活對象的大量跟蹤和對不可訪問(不可達)對象的掃描)由一個或多個垃圾收集器線程併發地完成。'minor collection'可以與正在進行的'major collection'交錯進行,類似於並行收集器的一種方式進行(尤其是,應用線程會在'minor collection'期間停頓)。

Concurrent Mode Failure

CMS收集器使用一個或多個垃圾收集器線程,這些線程與應用線程同時運行,其目標是在老年代滿之前完成對其的收集。
如前所述,在正常操作中,CMS在應用線程運行的同時執行大量跟蹤和清理工作,因此應用程序線程只能看到短暫的GC停頓。
然而,如果CMS收集器無法在老年代填滿前回收不可到達的對象,或者老年代的空閒空間無法滿足一次分配(時),應用暫停,在完成GC收集前所有應用線程將停止工作。不能併發地完成GC回收稱爲“併發模式故障”,這表明需要調整CMS收集器參數。如果併發回收被顯式垃圾回收(System.gc())中斷或因爲垃圾回收需要向診斷工具提供所需信息,則會報告併發模式中斷。

Excessive GC Time and OutOfMemoryError

如果太多的時間花費在垃圾回收,CMS會拋出一個OutOfMemoryError:如果超過98%的總時間花在垃圾收集上,而回收的堆不足2%,則拋出OutOfMemoryError。該特性旨在防止應用程序由於堆太小長時間無效運行。如果需要,可以通過在命令行中添加選項'-XX:-UseGCOverheadLimit'禁用此功能。

與並行收集器中的策略相同,只是執行併發回收所花費的時間不計入98%的時間限制。換句話說,只有在應用停止時執行的GC才計入98%時間限制。這種回收通常是由於併發模式失敗或顯式的GC請求(例如,對System.gc的調用)造成的。

Floating Garbage

CMS與Java HotSpot VM中的其它收集器一樣,是一個跟蹤收集器,它至少標識堆中所有可訪問的對象。用Richard Jones和Rafael D. Lins在他們的出版物《Algorithms for Automated Dynamic Memory》中的話說,它是一個增量更新收集器。由於應用線程和垃圾回收線程在'major collection'中併發運行,被垃圾收集器線程跟蹤的對象可能在回收過程結束時無法訪問。尚未回收的這種不可訪問對象稱爲“浮動垃圾”。“浮動垃圾”的數量取決於併發收集週期的持續時間和引用更新(也稱爲突變(mutations))的頻率。此外,由於年輕代和老年代是獨立收集的,互相作爲彼此的根源。作爲一個粗略的參考,可以嘗試將老年代的大小增加20%,以供給“浮動垃圾”。一次併發回收週期結束時堆中的浮動垃圾將在下一次回收週期中被回收。

Pauses

CMS在併發回收週期中兩次暫停應用。第一個暫停是標記根可達對象(例如,虛擬機棧中的對象引用和寄存器的對象引用、靜態對象等等)和堆中的其他地方(例如,年輕代)直接可訪問的對象標記爲活動對象。第一個暫停稱爲“初始標記暫停”(initial mark pause)。第二個暫停出現在併發跟蹤階段的末期,在CMS收集器跟蹤對象之後,找到那些在併發追蹤階段由於應用線程更新了引用而錯過標記的對象。第二個暫停稱爲“二次標記暫停”(remark pause)。

Concurrent Phases

對象可達路徑的併發跟蹤發生在“初始標記暫停”和“二次標記暫停”之間。在這個併發跟蹤階段,一個或多個回收線程可能會使用那些對應用線程可用的空閒的處理器資源。因此,即使應用程序線程沒有暫停,在此和其他併發階段,受計算限制的應用程序的吞吐量也可能會相應的降低。在“remark pause”之後,“併發清除階段”回收那些標識爲不可到達的對象。一旦回收週期完成,CMS就會等待,幾乎不消耗任何計算資源,直到下一個'major collection'週期開始。

Starting a Concurrent Collection Cycle

對於串行收集器,只要老年代已滿,就會發生一次'major collection',且在回收完成之前停止所有應用線程。相反,必須在併發回收開始時進行計時,確保回收能夠在老年代滿之前完成;否則,應用程序由於“Concurrent model failture”長時間暫停。有幾種方式可以開始一次併發回收。

根據最近的歷史記錄,CMS收集器維護了對老年代耗盡之前剩餘的時間以及併發回收週期所需的時間的評估。通過這些動態評估,將啓動一次併發回收,目的是在老年代耗盡之前完成一次併發回收週期。安全起見,對這些估計值進行填充,因爲“concurrent mode failure”的代價可能非常高。

如果老年代的佔用超過初始佔用(老年代的一個百分比),也將啓動一次併發回收。這個初始佔用閾值的默認值約爲92%,但是該值可能隨版本的不同而變化。可以使用命令行選項'-XX:CMSInitiatingOccupancyFraction=<N>'手動調整這個值,其中<N>是老年代大小的整數百分比(0到100)。

Scheduling Pauses

年輕代回收和老年代回收的暫停分別發生。它們不會重疊,但可能會連續發生,比如一個回收的暫停,緊接着另一個回收的暫停,就會出現一個更長的暫停。爲了避免這種情況,CMS嘗試將“remark pause”安排在上一次和下一次年輕代暫停之間的中間位置。這種調度目前沒有爲“initial mark pause”執行,“initial makr pause”通常比“remark pause”短得多。

Incremental Mode

注意,增量模式在Java SE 8中已經被棄用,可能在將來的主要版本中被刪除。

CMS可以以併發階段增量進行的模式使用。回想一下,在併發階段,垃圾回收線程使用一個或多個處理器。增量模式的目的是通過週期性地停止併發階段,將處理器交還給應用程序,從而減少長併發階段的影響。這種模式在這裏稱爲'i-cms',它將收集器併發地完成的工作劃分爲小塊時間,這些時間安排在年輕代收集之間。當需要CMS爲運行在具有少量處理器(例如,1或2)的機器上提供低GC停頓時,此特性非常有用。

併發回收週期通常包括以下步驟:

  • 停止所有應用線程,標識根可達的對象集,然後恢復所有應用線程(initial mark pause)。
  • 在應用線程運行時,使用一個或多個處理器併發地跟蹤可達對象。
  • 使用一個處理器併發地跟蹤自上一步跟蹤以來被修改的對象。
  • 停止所有應用線程,並重新跟蹤自上次檢查以來可能已修改的根和對象,然後恢復所有應用程序線程(remark pause)。
  • 使用一個處理器併發地將無法訪問的對象清除到分配使用的空閒列表。
  • 使用一個處理器,併發地調整堆的大小,併爲下一次回收週期準備支持數據結構。

通常,CMS收在整個併發跟蹤階段使用一個或多個處理器,而不會主動放棄它們。類似地,一個處理器用於整個併發清除階段,同樣不放棄它。對於具有響應時間約束的應用程序來說,這種開銷可能會造成太大的干擾,這些應用程序可能會使用處理內核,尤其是在只有一個或兩個處理器的系統上運行時。增量模式通過分解併發階段來解決這個問題,這些分解後的小併發階段安排在'minor collection'之中(即年輕代回收)。

Command-Line Options

表8-1 列出了控制i-cms模式的命令行選項:

Command-Line Options for i-cms

Recommended Options

Java SE8中使用i-cms,使用下面命令行選項:

  • -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode
  • -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

前兩個選項分別啓用了CMS和i-cms。後兩個選項是不需要的;它們只是將有關垃圾收集的診斷信息寫入標準輸出,以便查看和分析垃圾收集行爲。

對於Java SE 5和更早的版本,Oracle建議使用以下命令行選項作爲i-cms的初始設置:

  • -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode 
  • -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
  • -XX:+CMSIncrementalPacing -XX:CMSIncrementalDutyCycleMin=0
  • -XX:CMSIncrementalDutyCycle=10

同樣的值也推薦用於JavaSE8,儘管控制i-cms自動步調的三個選項的值已經成爲JavaSE6中的默認值。

Basic Troubleshooting

'i-cms'自動調速特性使用程序運行時收集的統計數據來計算佔空比,以便在堆滿之前完成併發回收。然而,過去的行爲並不能很好地預測將來的行爲,而且這些估計可能並不總是足夠準確,無法防止堆變得滿。如果發生了太多的'full collection',那麼嘗試表8-2中的步驟,“排除i-cms自動步調特性”,一次一個:

Troubleshooting the i-cms Automatic Pacing Feature

Measurements

示例8-1,“來自CMS收集器的輸出”是來自CMS收集器在使用'-verbose:gc'和'-XX:+PrintGCDetails'命令行時的輸出,刪除了一些"minor collection"細節。注意,CMS收集器的輸出與'minor GC'的輸出穿插在一起;通常,許多'minor collection'發生在併發收集週期中。CMS-initial-mark表示併發回收週期的開始,CMS-concurrent-mark表示併發標記階段的結束,CMS-concurrent-sweep表示併發清除階段的結束。以前沒有討論過CMS-concurrent-preclean所指的預清洗階段。預清洗表示在CMS-remark準備階段可以併發的完成的工作。最後一個階段是CMS-concurrent-reset,也是下一次併發回收的準備。
示例8-1:

Output from the CMS Collector

相對於'minor collection'的停頓時間,'initial mark'停頓通常較短。併發階段(併發標記、併發預清理和併發清除)通常比'minor collection'停頓持續的時間長得多,如示例8-1所示。但是,請注意,應用程序不會在這些併發階段暫停。'remark pause'通常可與'minor collection'的長度相比較。'remark pause'受某些應用程序特性的影響(例如,對象修改的高速率可能會增加此停頓),
以及自上一次'minor collection'以來的時間(例如,年輕代中的更多對象可能會增加此暫停)。

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