バッチ処理などスループット重視のアプリケーションはデフォルトのパラレルGCで良いが、Java EEアプリケーションサーバなどレスポンスタイム重視のものやHadoopなどのクラスタ系ソフトウェアで死活監視に引っ掛る系などのstop the worldをなるべく避けたいいわゆるサーバ系ソフトウェアを運用する場合には、UseConcMarkSweepGCを付与して停止時間の短いCMS GCを使う。その場合にCMSのチューニングに踏み込もうとするとなんだか難しい記述がいっぱいで若干困るので、簡単なガイドをメモとして書いておく。
対象バージョンは以下。
$ java -version java version "1.7.0_51" OpenJDK Runtime Environment (fedora-2.4.5.1.fc20-x86_64 u51-b31) OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
基本
このへんつけとけばいいんじゃないの。
- -Xmn, -Xms, -Xmx
- -XX:PermSize, -XX:MaxPermSize
- -XX:+UseConcMarkSweepGC
- -XX:+CMSClassUnloadingEnabled
- -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError
SurvivorRatio, TargetSurvivorRatio, MaxTenuringThreshold
基本的に指定する必要ないんじゃない。Java 5とか6とかの古いデフォルト値を載せててそれを変更しましょう的な情報がいっぱいあるので注意。Java 7のリリースが2011年7月なので、2012年より前のJVMオプション情報はJava 7では通用しない可能性が高く、基本的に信用しないで疑ったほうが良い。
あまり細かいオプションをこまごま指定するとアプリのバージョンアップとかでGCの傾向がごっそり変わってしまったりして毎回チューニングやりなおしというような苦労が発生することがあるのでほどほどにね。
CMS開始条件
CMSが開始されるトリガーは二つある。ひとつはOld領域の利用率がCMSInitiatingOccupancyFractionに到達した場合。もうひとつは今CMS走らせないと先にヒープ埋まっちゃうよね、という統計判断を元にしたトリガー。
UseCMSInitiatingOccupancyOnlyを付与すると後者のトリガーが無効になる。
CMSInitiatingOccupancyFractionのデフォルト値は-1で、-1の場合は他の値を元に計算される。
CMSInitiatingOccupancyFraction = 100 - MinHeapFreeRatio + CMSTriggerRatio * MinHeapFreeRatio / 100
これらのデフォルトはMinHeapFreeRatio=40、CMSTriggerRatio=80なので、CMSInitiatingOccupancyFractionのデフォルト値は92になる。つまり、Old領域が92%になったときに最初のCMS GCが行われる。
オブジェクトアロケーションが激しいようなアプリケーションでは92%だと手遅れになることがあるのでCMSInitiatingOccupancyFractionは下げたほうが良い。70とか。
後者の統計については細かい話になるので省略する。知りたい人はソース嫁。ただ、ここで使われるヒープ空き容量の安全係数CMSIncrementalSafetyFactorというオプションはたまに使う。このオプションはCMSIncrementalModeでのみ有効なオプションのように見えるが、実際はそうではない。
世に出ているチューニング記事でUseCMSInitiatingOccupancyOnlyを有効するものが多くあるが、無闇に有効にする必要はない。ただ、有効にしたほうがトリガーがわかりやすくなるので、そちらのほうが総合的にチューニングしやすくなる場面はある。
CMSIncrementalMode
Java 8で非推奨なのでもう忘れろ。
UseParNewGC, CMSParallelRemarkEnabled
デフォルトtrueだから忘れろ。
CMSPermGenSweepingEnabled
Java 5でしか使わないので忘れろ。
CMSClassUnloadingEnabled
デフォルトfalse。ClassLoader.defineClass()を利用しているバイトコードエンハンサ使っていて、無尽蔵にクラスを動的定義してロードしてPerm埋まるような場合はtrueにする。アプリが未定とかよくわからない場合も安全に倒してtrueにしておいたほうがいいと思う。trueにするとGC中にクラスのアンロードをするが、その代償としてremarkフェーズのGCポーズが増えるらしい。Java 8からデフォルトtrueになったけどPermなくなったのでどうなった(まだ追っかけてない)。
java.lang.reflect.Proxyによるクラス生成はClassLoader.defineClass()を利用しておらず、sun.reflect.ClassDefiner, sun.misc.UnsafeのdefineClassを使っているので上の問題には該当しない。
デフォルト値を調べる
PrintFlagsFinalつけてgrepする。UseConcMarkSweepGCなどの指定でデフォルト値が変わったりするものがあるので、調べる対象のjavaのオプション以外のオプションを実際に渡すこと。
java -XX:+UseConcMarkSweepGC -XX:+PrintFlagsFinal -version | grep -E "SurvivorRatio|MaxTenuring"
CMSでも無理
CMSでパフォーマンスターゲットを達成できない場合はG1GCかAzul Zingを使うことになる。G1GCの利用の基本についてはGarbage First Garbage Collector (G1 GC) - Migration to, Expectations and Advanced Tuningというスライドが一番まとまっている。