nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

Java 7 CMS GCの基本的な情報の整理

バッチ処理などスループット重視のアプリケーションはデフォルトのパラレル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でパフォーマンスターゲットを達成できない場合はG1GCAzul Zingを使うことになる。G1GCの利用の基本についてはGarbage First Garbage Collector (G1 GC) - Migration to, Expectations and Advanced Tuningというスライドが一番まとまっている。