2018.01.31
Concurrent GC
概要
CMS CollectorはTenured GenerationのGC処理をApplictaion ThreadのPauseを最小限に抑えながらConcurrentに進行します。CMS CollectorによるGC作業をConcurrent GCと呼びます。SWT(Stop The World)方式のFull GCが持つ欠点を最小限に抑え、応答時間を最適化するために設計されたGC方法です。
動作
段階
Concurrent GCは、以下の手順で行われます。
1. Initial Mark Phase:Root Objectから直接参照されるObjectをMarkingします。 この段階で、すべてのApplication ThreadはStopされます。 2. Concurrent Mark Phase:Initial Mark PhaseでMarkされたObjectによって参照されるObjectと共にMarkingします。 このステップは、Concurrentに行われます。 3. Concurrent Preclean Phase:次の段階であるRescan段階の負担を軽減するため、Concurrent Mark段階で追加/変更 されたObjectを事前にスキャン(Scan)しておきます。このステップは、Concurrentに行われます。 4. Rescan Phase:前の手順でScanされていない残りのObjectをすべてScanします。 この段階で、すべてのApplication ThreadはStopされます。 5. Concurrent Sweep Phase:前の手順でMarkれていないObject、すなわちDead Objectをきれいにします。 このステップは、Concurrentに行われます。 6. Concurrent Reset Phase:Mark&Sweepのステップが終わった後のクリーンアップ操作では、CMSの操作に必要なデータ 構造を初期化する作業が行われます。このステップは、Concurrentに行われます。
全6段階のうちInitial Mark PhaseステップとRescan PhaseだけSTW方式で動作し、残りの手順は、すべてのConcurrentに行われます。したがって、ユーザーが体感するGC Pause時間を最小限に抑えることができるのです。
例
以下にConcurrent GCによるGC Logの簡単な例を示します。
- Initial Mark Phase 40.146: [GC [1 CMS-initial-mark: 26386K(786432K)] 26404K(1048384K), 0.0074495 secs] -- Concurrent Mark Phase 40.154: [CMS-concurrent-mark-start] 40.683: [CMS-concurrent-mark: 0.521/0.529 secs] -- Concurrent Preclean Phase 40.683: [CMS-concurrent-preclean-start] 40.701: [CMS-concurrent-preclean: 0.017/0.018 secs] -- Rescan Phase 40.704: [GC40.704: [Rescan (parallel) , 0.1790103 secs] 40.883: [weak refs processing, 0.0100966 secs] [1 CMS-remark: 26386K(786432K)] 52644K(1048384K), 0.1897792 secs] -- Concurrent Sweep Phase 40.894: [CMS-concurrent-sweep-start] 41.020: [CMS-concurrent-sweep: 0.126/0.126 secs] -- Concurrent Reset Phase 41.020: [CMS-concurrent-reset-start] 41.147: [CMS-concurrent-reset: 0.127/0.127 secs]
Troubleshooting
Permanent GenerationとFull GC
一般的にFull GCが発生すれば、Permanent Generationに対するCollection作業も一緒行われます。しかし、Concurrent GCはPermanent Generationに対するCollectionを実行しません。 したがって、Class Loading/Unloadingが頻繁なApplicationではPermanent Generationが満杯になる現象が発生することになります。Permanent Generationが満杯になればJVMはConcurrent方式でないSTW方式のFull GCを実行します。
この問題を避けるには次の二つのオプションを活性化しなければなりません。
・ -XX:+CMSPermGenSweepingEnabled ・ -XX:+CMSClassUnloadingEnabled オプションの詳細な説明は、JVM Optionsを参照してください。
以下にPermanent GenerationによるFull GCが発生した状況のGC Log例を示します。12Mサイズがメモリ領域が一杯になり、それによってFull GCが発生することを確認することができます。
21.641: [Full GC 21.641: [CMS: 12167K->10175K(17064K), 0.3359514 secs] 15184K->10175K(21096K), [CMS Perm : 12287K->1376K(12288K)], 0.3364375 secs]
Tenured Generation FragmentationとFull GC
Concurrent GCは一般的なFull GCとは違ってTenured Generationに対するCompaction作業を毎回実行しません。したがってメモリー領域がフラグメンテーション(Fragmentation)になる確率が高くなります。 Tenured Generationが断片化されれば、Young GenerationにあるObjectに対するPromotion作業が失敗する確率が高まります。 たとえば1M大きさのChunk100個になっているTenured Generationは余裕メモリーが100Mに達するが、2M大きさのObjectを格納できなくなります。 こういう現象をよくPromotion Failureだと呼びます。 Promotion Failureが発生すればJVMは直ちにFull GCを実行します。
この問題を避けるために次のような方法を実行することができます。
・ -XX:CMSFullGCsBeforeCompaction=0オプションと-XX:+ UseCMSCompactAtFullCollection オプションを使用します。(詳細については、JVM Optionsを参照してください。) ・ -XX:CMSInitiatingOccupancyFraction=オプションと-XX:+ UseCMSInitiatingOccupancyOnly オプションを使用します。(詳細については、JVM Optionsを参照してください。) ・ Young Generationのサイズを減らします。Young Generationのサイズが小さいと、比較的少ない数のObjectが Promotionされ、その分Promotion Failureが発生する確率が減少します。
以下にPromotion Failureが発生した状況のGC Log例を示します。
197.976: [GC 197.976: [ParNew: 260872K->260872K(261952K), 0.0000688 secs] 197.976: [CMS197.981: [CMS-concurrent-sweep: 0.516/0.531 secs] (concurrent mode failure): 402978K->248977K(786432K), 2.3728734 secs] 663850K->248977K(1048384K), 2.3733725 secs]