JVMのヒープ・GCチューニングまとめ

http://ise0615.blogspot.com/2010/06/javagc.html

のコピペ。自分用に

■ 前提条件
-----------------------------------------------
 JVMは、Sun Java (JDK 1.5-1.6)を想定。

■ 目標

 ・マイナーGC、フル GCがそれぞれ頻発しないこと。

 ・フル GCの実行時間が1秒未満であること。

 ・マイナーGCの実行時間が0.1秒未満であること。

 ・連続した負荷状態(想定されるピークアクセス)でもOutOfMemoryErrorが   発生しないこと。

 ・理想的な状態は、上記に加えて、フル GCの発生が低頻度であること。

 具体的には、できるだけマイナーGCで短命オブジェクト  (1回使ったらもう使わないようなオブジェクト。  逆にセッションオブジェクト等は長命オブジェクトとなる)を破棄させて、  短命オブジェクトが、TenuringThresholdを超えるマイナーGCを経て  New領域からOld領域へ移動することをできるだけ抑えることがチューニング目標となる。

 ※Old領域に行ってしまうとフル GCでしか掃除されないため、できるだけ   Old領域に移る前にNew領域で掃除させる。  ※実際のTenuringThresholdが、-XX:MaxTenuringThresholdで指定した値に   できるだけ近い状態を維持する。

■ 運用環境で是非設定しておくべきJava起動オプション

 -server サーバーモードを有効化  -Xms Javaヒープ初期サイズ。一般的に、-Xmxと同値にする  -Xmx Javaヒープ最大サイズ  -Xmn(-XX:NewSize) New領域初期サイズ。一般的に、-XX:MaxNewSizeと同値にする  -XX:MaxNewSize New領域最大サイズ  -XX:PermSize Permanent領域初期サイズ。一般的に、-XX:MaxPermSizeと同値にする  -XX:MaxPermSize Permanent領域最大サイズ  -verbose:gc(-Xloggc:path_to_file) GCログ出力を有効化  -XX:+PrintGCTimeStamps GCログにタイムスタンプ(Java起動時からの経過時間)を出力  -XX:+PrintGCDetails GCログを詳細に出力(New領域とJavaヒープそれぞれが   どれだけ減ったか出力される)  -XX:+PrintClassHistogram*1 SIGQUITシグナル受信時にヒープ統計情報を出力   (出力時に強制的にフル GCが発生)。-verbose:gc(-Xloggc:path_to_file)と併用必須。   なお、SIGQUITシグナルを送信するには、kill -3 を実行すればよい  -XX:+HeapDumpOnOutOfMemoryError OutOfMemoryError発生時にヒープダンプを出力   (Sun Java 1.4.2_12以上、1.5.0_07以上、1.6以上)

■ 運用環境で設定しておいた方がいいJava起動オプション

 -XX:SurvivorRatio Eden領域のサイズをS0またはS1領域のサイズで割った値   (S0とS1領域は同じサイズ)  -XX:MaxTenuringThreshold New領域において、オブジェクトがマイナーGCを   何回超えて生き残ると、Old領域に移動するかのしきい値の最大値   (実際のしきい値は、1からMaxTenuringThresholdの範囲で動的に決定される)  -XX:TargetSurvivorRatio Survivor領域がいっぱいと判断される使用率

■ チューニング(トラブル対応)のためのコマンド、ツール

 1.GC発生状況、メモリリーク状況

  -verbose:gc(-Xloggc:path_to_file)   -XX:+PrintGCTimeStamps   -XX:+PrintGCDetails

  を設定しておくことでGCログが取得できる。   取得したGCログは、侍やGCViewer等でグラフ化できる。

 2.ヒープ使用状況

  jstatコマンドでリアルタイムに確認可能。   (-gcutilオプションで、S0、S1、Eden、Old、Permanent領域ごとの使用率や   マイナーGC、フル GCの発生回数、GCの実行時間累計が確認可能)

  jstat -gcutil -h10 5000

  jconsoleコマンドを使用すれば、GUIでリアルタイムに確認することも可能。

 3.ヒープ統計情報

  以下の方法で出力できる。

  -verbose:gc(-Xloggc:path_to_file)、-XX:+PrintClassHistogramを   設定しておき、kill -3 を実行   jmap -histo

  ヒープ中のオブジェクトのインスタンス数、サイズ一覧が出力される。   ヒープダンプの出力に比べて、取得時の負荷は小さいが、分析は大変。

 4.ヒープダンプ

  以下の方法で出力できる。

  -XX:+HeapDumpOnOutOfMemoryErrorを設定しておき、OutOfMemoryErrorが発生   -XX:+HeapDumpOnCtrlBreak*2を設定しておき、kill -3 を実行   jmap -heap:format=b (Java 1.5の場合)   jmap -dump:format=b,file=filename (Java 1.6の場合)

  出力に時間もかかる(ヒープサイズ512MBで5-10分程度)し、負荷も大きいが、   詳細なヒープ情報を取得可能。ダンプファイルはバイナリなので、   以下のツールで分析する。

  ・Eclipse Memory Analyzer   ・HeapAnalyzer   ・HAT

 5.プロファイラ

  オブジェクトの生成、GC等、JVMの挙動をトレースする。   アプリケーションを動作させつつ、増加していくオブジェクトを探したり、   スタックトレースを確認したり等の調査が可能。   パフォーマンスが悪化するため、運用環境では通常使用できない。   メモリリークやメモリの大量消費の調査の場合、まず問題の再現方法を   突き止めた上で、開発/検証環境でプロファイラを使用して、再現方法を   実施して、増加したオブジェクトを解析する。

  ・NetBeans Profiler

 6.スレッドダンプ

  ヒープ、GCチューニングの観点ではないが、アプリケーションの応答が   とまってしまったらとりあえず取得するのがよい。kill -3 で出力される。   スレッドのデッドロックが発生していないか、大量の待ち行列が発生して   いないか等がわかる。

■ ケース別チューニング(トラブル対応)方法

 1.OutOfMemoryError: PermGen space と出る場合

  Permanent領域不足によるOutOfMemoryErrorが発生している。

  対応としては、-XX:PermSize、-XX:MaxPermSizeを指定し、   OutOfMemoryErrorが発生しないようにPermanent領域を広げてやればよい。   ただし、基本的にPermanent領域は、クラス情報やメソッド情報が格納される   領域であり、アプリケーション起動後はほとんど増減しない。   jstatやjconsole等でPermanent領域の使用状況を確認し、もし増加が続くよう   であれば、Permanent領域のメモリリークが発生している可能性がある。

  リーク箇所を発見するには、ヒープ統計情報やヒープダンプを数回取得して   比較したり、アプリケーションログ等からOutOfMemoryError発生直前に実行   されている処理がいつも同じでないか等を確認する。

  例えば、DIコンテナは通常1アプリケーションで1つしか使用しないが、   とある処理で新規にコンテナを生成するようになっていて、   その処理が実行されるたびに大量のPermanent領域を消費し、   ついにはOutOfMemoryErrorが発生した事例がある。

 2.OutOfMemoryError: Java Heap Space と出る場合   (OutOfMemoryErrorとしか出ないこともある)

  ヒープ不足によるOutOfMemoryErrorが発生している。

  とりあえずの対応としては、-Xms、-Xmxを指定し、OutOfMemoryErrorが   発生しないようにヒープを広げてやればよい。

  根本対応としてはまずは、GCログを取得し、ヒープでのメモリリークが   発生していないか確認する必要がある。   GCログを侍やGCViewer、Excel等でグラフ化して確認する。   グラフがフル GCを跨って、右肩上がりになっている   (右肩上がりになってフル GCでいったん下がるが、スタート時ほど   下がっていない   (複数回のフル GC発生時の下限値を結ぶ線が右肩上がりになっている))場合は、   メモリリークが発生している可能性が高い。   リーク箇所を発見するには、ヒープ統計情報やヒープダンプを数回取得して   比較して、フル GCを経ても増加し続けているオブジェクトを探す。   怪しいオブジェクトが見つかれば、NetBeans Profiler等を使用して、   オブジェクトの使用箇所を探し、オブジェクト参照を追跡する。

 3.ヒープチューニング方法

  運用環境で、jstat -gcutilやjconsoleを使用してヒープの各領域の使用状況を   確認しながら、フル GC実行時間を最小にするために、OutOfMemoryErrorが   発生しないサイズでなるべく小さいサイズを-Xms、-Xmxに指定し、また、   フル GC発生頻度を抑えるために、

  -Xmn(-XX:NewSize)   -XX:MaxNewSize   -XX:SurvivorRatio   -XX:MaxTenuringThreshold   -XX:TargetSurvivorRatio

  を調整して、なるべくNew領域でのマイナーGCでメモリを解放させて、   Old領域へ移動される頻度、サイズを減らすようにする。

  マイナーGCが発生していないのに、いきなりOld領域の使用率が上がった場合や、   S0、S1、Eden領域がいきなり100%になった場合は、オブジェクトのサイズが   S0、S1、Eden領域サイズよりも大きい。   アクセスログと突き合わせて、使用率のあがるURLを特定し、コードを見たり、   URLアクセス前後のヒープ統計情報、ヒープダンプを比較することで、   サイズの大きいオブジェクトを特定する。   もし、そのオブジェクトが短命オブジェクトの場合は、-Xmn(-XX:NewSize)、   -XX:MaxNewSizeを増加させて、Old領域に行かないように調整する。   とりあえずは、以下のような値からはじめて調整していく。

  -Xmn(-XX:NewSize)   -XX:MaxNewSize   -Xmxサイズの1/4 - 1/3程度   -XX:SurvivorRatio 2 - 8程度   -XX:MaxTenuringThreshold 32程度   -XX:TargetSurvivorRatio 80 - 90程度

 4.マイナーGC実行時間が1秒を超える場合

  New領域が大きすぎる可能性があるので、-Xmn(-XX:NewSize)、   -XX:MaxNewSizeを減らす。CPUが複数ある(マルチコアを含む)場合は、   -XX:+UseParallelGC オプションでパラレルGCを有効にする。   -XX:+UseParallelGC マイナーGCをマルチスレッドで実行

 5.フル GC実行時間が1秒を超える場合

  まずは、メモリリークが発生していないか確認する。   確認方法は、2.OutOfMemoryError: Java Heap Space と出る場合 を参照。

  メモリリークが発生していない場合は、Old領域が大きすぎる可能性があるので、   -Xms、-Xmxを減らす。減らしすぎて、OutOfMemoryErrorを発生させないように注意する。   やり方は、3.ヒープチューニング方法 を参照。

  メモリリークが発生しておらず、これ以上減らすとOutOfMemoryErrorが発生する   Old領域サイズなのに、フル GC実行時間が1秒を超える場合は、   以下の対応案が考えられる。

  (1)アプリケーションでメモリを使いすぎていないか確認し、    使いすぎている場合は修正する

   メモリを多く消費する処理を特定するには、運用環境で、jstat -gcutilや    jconsoleを使用してヒープの各領域の使用状況を監視し、使用率がドカッと    あがったときに実行された処理を、アクセスログから突き止める。    (ある程度絞れれば、あとは開発/検証環境で突き止める)あとは、    コード解析を行い、修正する。

  (2)セッションタイムアウト時間の確認

   セッションタイムアウト時間が不必要に長すぎないか確認し、長い場合は短くする。

  (3)コンカレントGCの使用

   フル GCをできるだけアプリケーションをとめずに並行実行するコンカレントGCを使用する。    以下のJava起動オプションを設定する。

   -XX:+UseConcMarkSweepGC コンカレントGCの有効化    -XX:+CMSParallelRemarkEnabled フル GCのRemarkフェイズをマルチスレッドで実行    -XX:+UseParNewGC マイナーGCをマルチスレッドで実行

 6.アプリケーションの応答が停止(フリーズ)した場合

  スレッドダンプを取得する。

 7.StackOverflowError と出る場合

  StackTraceを確認し、メソッドの不要な再帰呼び出し等が行われていないか確認する。   あるいは、以下のJava起動オプションを設定し、スタックサイズを増やしてみる。

      |HotSpot VMの場合              |非HotSpot VMの場合   ------------------------------------------------------------------------------------   -Xss  |(Java/ネイティブ)スレッドのスタックサイズ  |ネイティブスレッドのスタックサイズ   ------------------------------------------------------------------------------------   -Xoss  |効果なし*3                 |Javaスレッドのスタックサイズ