まじめにJVMチューニング: 第1回 まずは現状確認

2014-01-24

まじめにJVMチューニングに取り組む機会があったので、忘れないようにこの記事を書きました。
GCアルゴリズムやパラメータの説明はいろいろなサイトに掲載されていたのですが、
「どうやって取り組むか」「何を見れば良いか」は意外とまとまっていなかったので。

JavaやScala製のアプリケーションはもちろんのこと、
Solr, ZooKeeper, Cassandra, Neo4J等jvm上で動くプロダクトを使う場合つねに
「JVMパラメータをどう設定するか」
という課題があります。

最近のミドルウェアでは、インストール時に最適なオプションを渡して
javaを実行するようなシェルをつくるrpmを提供していたりしますが。

さて、今回私が取り組んだのはフルGC対策のためのチューニングです。
フルGCとは何かというと、
別名「Stop the World」と呼ばれ、全てのアプリケーション・スレッドを停止させて古いメモリ領域をクリーニングするガベッジコレクタの動作のことを言います。
フルGC中はすべてのアプリケーション・スレッドが停止してしまうため一切の応答がなくなってしまう恐ろしい現象です。

アプローチ

  1. まずは現状確認
  2. GCログをみる
  3. 方針たててパラメータいじってみる
  4. GCアルゴリズムを変えてみる

1. まずは現状確認

まずは、現在、どのようなパラメータで動いているのかを確認します。
もし、明示的にパラメータを指定していないのだとしたら、デフォルト値としてどのような値が適用されるのかを確認します。

「-XX:+PrintFlagsFinal」オプションを指定して「java -version」を実行します。

$ java -XX:+PrintFlagsFinal -version
[Global flags]
    uintx AdaptivePermSizeWeight                    = 20              {product}
    uintx AdaptiveSizeDecrementScaleFactor          = 4               {product}
     :

どのようなフラグが設定されているかが出力されます。
ここで行うチューニングのポイントとなるフラグだけ、下記に抜き出してみます。

$java -XX:+PrintFlagsFinal -version
[Global flags]    uintx CMSIncrementalDutyCycle                   = 10              {product}
    uintx CMSIncrementalDutyCycleMin                = 0               {product}
     bool CMSIncrementalMode                        = false           {product}
     bool CMSIncrementalPacing                      = true            {product}
    uintx G1HeapRegionSize                          = 0               {product}
    uintx G1ReservePercent                          = 10              {product}
    uintx InitialHeapSize                          := 32878848        {product}
    uintx MaxGCMinorPauseMillis                     = 18446744073709551615{product}
    uintx MaxHeapSize                              := 698351616       {product}
    uintx MaxPermSize                               = 174063616       {pd product}
     intx MaxTenuringThreshold                      = 15              {product}
    uintx MinSurvivorRatio                          = 3               {product}
     intx NewRatio                                  = 2               {product}
    uintx NewSize                                   = 1310720         {product}
    uintx OldSize                                   = 5439488         {product}
    uintx PermSize                                  = 21757952        {pd product}
     bool PrintGCDetails                            = false           {manageable}
     bool PrintGCDateStamps                         = false           {manageable}
     intx SurvivorRatio                             = 8               {product}
     bool UseConcMarkSweepGC                        = false           {product}
     bool UseG1GC                                   = false           {product}
     bool UseParallelGC                            := true            {product}
     bool UseSerialGC                               = false           {product}
java version "1.7.0_45"
OpenJDK Runtime Environment (rhel-2.4.3.2.el6_4-x86_64 u45-b15)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)

また、

OpenJDK 64-Bit Server VM

の部分から、サーバモードで動くかクライアントモードで動くかが確認できます。

Java6から、64bitJDKであれば-serverオプションがデフォルトとなりました。
詳しくはこちら
Server-Class MachineDetection

上記の出力から、私の環境のデフォルト値は

  • jdk: OpenJDK 1.7.0_45 64bit
  • mode: server
  • GCアルゴリズム: ParallelGC
  • 最大ヒープサイズ: 666MB
  • 初期ヒープサイズ: 31.4MB
  • 最大Permサイズ: 166MB
  • 初期Permサイズ: 20.8MB

となっていることが読み取れます。

オプションを渡してjavaを実行することで、これらの値は上書きされます。

たとえば、

java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC -version

のように実行すると

 bool UseConcMarkSweepGC                       := true            {product}
 bool UseParallelGC                             = false           {product}

のようにGCアルゴリズムが変更されます。

まじめにJVMチューニング第2回: GCログをみる


こちらの動画で詳しいチューニングのやり方も解説しています。

スクリーンショット 2015-08-24 10.01.11

VisualVMでjavaプロセスを可視化してみよう part1