Java
AdventCalendar
G1GC
JavaDay 21

3分で理解するG1ガベージコレクション

Java Advent Calendar 2017 21日目の記事です。
Java9が今年リリースされてHotSpot JVMのデフォルトGCがG1になったので、おさらいのために投稿します。

GCの種類

HotSpot VMには4つのGCアルゴリズムが実装されている。

  • シリアル型
  • パラレル型(スループット型)
  • CMS
  • G1

シリアル型

シングルコアCPUマシンではこれがデフォルトGCとなる。GCの処理はシングルスレッドで実行される。マイナーガーベージコレクションやフルガーベージコレクションにおいて、アプリケーションはすべて停止する。

01.png

パラレル型(スループット型)

マルチコアCPUマシンではこれがデフォルトGCとなる。GCの処理はマルチスレッドで実行するため、シリアル型よりも高速でGCが実行される。
Java7u4以前ではyoung領域の処理のみマルチスレッドであり、old領域の処理にもマルチスレッドを使う場合は、-XX:+UseParallelOldGCオプションの指定が必要である。Java7u4以降はyoung領域、old領域ともにマルチスレッドGCがデフォルトである。

02.png

CMS(Concurrent Mark Sweep)

フルガーベージコレクションに伴う長時間のアプリケーション停止を回避する目的で設計されたGCである。マイナーガーベージコレクションではシリアル型やパラレル型と同様にアプリケーションスレッドは停止する。
フルガーベージコレクションではアプリケーションスレッドを止めずに、1つ以上のスレッドを使ってバックグラウンドでold領域を探索し、使用されていないオブジェクトを破棄する。
結果、アプリケーションスレッドが停止する合計の時間はスループット型より短くなる。

03.png

G1GCて何?

  • G1(Garbage 1st)
  • アプリケーションスレッドと並行でGC処理するのでCMSと同じコンカレント型
  • 世代別GCという点では今までのGCと同じ
  • ヒープサイズが大きいアプリ(おおよそ4G以上)に適している
  • Java6から実装はされている。ただし、Java7u4までは試験的な実装で本番で使うならJava8以降がおすすめ

ヒープの管理

従来(シリアル、パラレル、CMS)のヒープ管理

05a.png

G1のヒープ管理

ヒープをリージョンに分割して管理している。
04a.png

リージョンのサイズ

デフォルトの設定ではリージョンサイズはヒープサイズによって決定する。

ヒープサイズ リージョンサイズ
4GB未満 1MB
4GB以上8GB未満 2MB
8GB以上16GB未満 4MB
16GB以上32GB未満 8MB
32GB以上64GB未満 16MB
64GB以上 32MB

-XX:G1HeapRegionSize=Nフラグを使い自分でサイズを指定することも可能。0はデフォルト値。
Nは2のべき乗である必要があり、それ以外の値が指定された場合は最も近い2のべき乗値へ切り下げられる。

ほとんどの場合、デフォルト値でもOKだが次のようなケースではチューニングを求められる。
例えばヒープの変動幅が大きい場合(-Xms4G -Xmx16G)、デフォルトではリージョンサイズは1MBとなり、ヒープが拡大さるとリージョン個数が16,000個となる。G1GCはリージョン個数が2,048程度を前提に設計されているためGC効率が悪くなる。このようなケースでは上記オプションの指定によりリージョン個数が2,048個前後になるようサイズ調整する。

G1GCのアルゴリズム

G1では主に4つの処理が行われる。

young領域へのGC

eden空間がいっぱいになるとyoung領域へのGCが発生する。
GCが完了すると、eden空間は空になりリージョンは解放される。最低1つのsurvivor空間が割り当てられ、一部のオブジェクトはOld領域へ移される。
06a.png

コンカレントサイクル

コンカレントサイクルではold領域の不使用とされるリージョンに対してマーク(図中のX)をつけることがメイン。マーク付けの前にyoung領域へのGCを実行する。

コンカレントサイクルはいくつかのフェーズに分かれる。
1. 初期マーク付け(initial-mark)
2. ルートリージョンスキャン(root-region-scan)
3. 並列マーク付け(concurrent-mark)
4. 再マーク(remark)
5. クリーンアップ(cleanup)
6. 並列クリーンアップ(concurrent-cleanup)

この中でアプリケーションスレッドが停止するのは1, 4, 5である。
07a.png

混合GC

eden空間を完全に空にするとともに、survivors空間も調整される。old領域ではコンカレントサイクルでマークされたリージョンの一部が解放される。また、マークが付いたリージョンで使用中のデータも別のリージョンに移動することでヒープの断片化を低減する。
1回のGCでマークがついたすべてのリージョンを解放するのではなく、混合GCを繰り返すことでconcurrent mode failureが発生しないようにする。
08a.png

フルガーベージコレクション

これはいつものフルガーベージコレクション。
G1ではフルガーベージコレクションは避けるべき。発生する場合はチューニングを行う必要がある。
フルガーベージコレクションが発生する主な原因は以下の4つ。
- concurrent mode failureの発生
- 昇格の失敗
- 移動の失敗
- 巨大オブジェクトの割り当ての失敗

※これらの発生原因とチューニング方法はいつか書く。

使いどころ

どのGCを使うかはパフォーマンスの目標値や現状のCPU使用量をもとに検討すること。

Web系

  • フルガーベージコレクションにともなう一部リクエストへの影響を最小限にするならコンカレント型がおすすめ。
  • 異常値(フルガーベージコレクションの影響を受けたリクエスト)を外したレスポンスタイム値の平均を重視するならスループット型がよい結果を得られる。
  • コンカレント型であれば長い停止は避けられるが、CPU使用量は増加する。

バッチ系

  • CPU使用率に余裕があるならコンカレント型がよい。フルガーベージコレクションを避け処理を迅速に終えることができる。
  • CPU使用率に余裕がない場合にコンカレント型を利用すると、処理完了がかえって延びる。

最後に

GCはJava(JVM)の肝。どんなシステムのどのJavaバージョンでも避けて通れないのがメモリ関連のパフォーマンス問題。この新しくはないけどシレッとデフォルトになったG1GCを多少なりとも理解しておきたい。

告知

2018年1月20日(土)「JavaOne2017報告会 in OKINAWA」やります!
豪華ゲストも来ます!
シーズンオフの沖縄にぜひお越しください。
https://java-kuche.doorkeeper.jp/events/68540
https://java-kuche.doorkeeper.jp/events/68542