チューニングに使えるJava性能監視ツール
ヒープ領域とパーマネント領域
JavaVMには、独自のメモリー管理機構が搭載されています。不要になったオブジェクトを定期的に破棄してメモリーを開放するガベージ・コレクション機能と、永続的に使われるオブジェクトであるかどうかを判定する機能が搭載されています。
JavaVMが確保するメモリーには、大きく3つあります。オブジェクトを管理するヒープ領域と、読み込むクラス情報を確保するパーマネント領域、それ以外に、ランタイムが必要とするシステム領域です。
ヒープ領域は、必要に応じて保存と破棄が繰り返される領域となります。クラス情報は、パーマネント領域に格納されます。それ以外に確保されるメモリーとして、ランタイムが利用するシステム・メモリー(OS依存ヒープ・メモリー)とスレッド管理のメモリーが別領域になります。
図2: Javaヒープ領域、パーマネント領域、システム領域 |
- New領域: ヒープ領域
New領域は、Eden、Survivor0、Survivor1(FromとToとも呼ばれます。略称はS0、S1です)の3つの領域に分かれています。
オブジェクトが新たに作成されると、そのオブジェクトは、まず最初にEden(E)領域に配置されます。
Eden領域が新しいオブジェクトで一杯になると、Scavenge GC(スカベンジGC、New領域のみのガベージ・コレクション。YGCとも呼ばれる)が実行されます。使用されていないオブジェクトは、破棄されます。現在も使用されていると判断されたオブジェクトは、SO(From)領域またはS1(To)領域へ移動し、メモリー上に残されるオブジェクトの候補となります。YGCが起動すると、Eden領域はクリアされ、再び新しいオブジェクトを受け入れるようになります。
New領域が不足するとScanvengeGC(YGC)が実行されます。ScanvengeGCは、頻ぱんに起こるのが普通です。ただし、あまりにも多く発生する場合は、少なからずパフォーマンスに影響するので、New領域を増やすことも検討します。
YGCを一定回数繰り返した後でも、JavaVMによって使用頻度が高いと判断されたオブジェクトは、破棄されずに生き残ることになります。こうして、次に説明するOld領域へ移動します。判断基準となる値は、MaxTenuringThresholdの値です。デフォルトは32です。
図3: New領域 - Old領域: ヒープ領域
Tenured Generation、またはOld Generationとも呼ばれます。略称はOです。New領域を対象としたScanvengeGC(YGC)後に生き残ったオブジェクトが配置されます。ここに配置されたオブジェクトは、ランタイム中に何度も利用される寿命の長いオブジェクトとして判断されたものです。
Old領域が一杯になると、FullGC(フルGC、完全ガベージ・コレクション)が行われます。FullGCが実行されると、ランタイムがいったん停止し、Old領域に退避されていた不要なオブジェクトの破棄を最優先するようになります。FullGCが頻発すると、パフォーマンスが悪くなります。
図4: Old領域 - Permanent領域: 非ヒープ領域
クラスやメソッドの情報が格納されます。
クラスを大量に読み込むライブラリやフレームワークを利用する場合、また、JSP(Java Server Pages)を大量に使用する場合などは、Permanentサイズを大きく設定する必要があります。特に、JSPは、Javaコンテナ(Servletコンテナ)によってクラス・ファイルを1つ生成するため、実質上JSPはクラス1つと同等です。Permanent領域が不足する際にもFullGC(完全ガベージ・コレクション)が行われ、必要に応じてクラスの読み直しが発生します。
Old領域やPermanent領域が不足すると、FulGC(完全ガベージ・コレクション)が起動します。このため、可能な限りFullGCが発生しないよう、Javaランタイムのヒープ・サイズを調整します。
特に、Permanent領域が不足すると、OutOfMemoryが検出されます。ランタイムが停止することになるので、不足しないように調整します。もし、すぐにPermanentが不足するようであれば、何らかのメモリー・リークがあるか、確保しているメモリー・サイズが小さいことを意味します。
以上に挙げた、これらのメモリー設定は、Javaランタイムを起動するオプションで設定します。
Javaランタイム・メモリー設定の例
java …(起動クラス)… \ -Xms512m \ -Xmx512m \ -XX:NewSize=64m \ -XX:MaxNewSize=128m \ -XX:PermSize=64m \ -XX:MaxPermSize=128m
指定できるオプションの一覧は、以下の通りです。
表3: Javaメモリー設定オプション
オプション | 指定する内容 |
---|---|
-Xms | 初期ヒープ・サイズ |
-Xmx | 最大ヒープ・サイズ |
-Xmn(-XX:NewSize) | New世代領域サイズ |
-XX:PermSize | Permanent領域初期サイズ |
-XX:MaxPermSize | Permanent領域最大サイズ |
-XX:NewRatio | New世代領域とOld世代領域の比率(Old世代領域/New世代領域) |
-XX:SurvivorRatio | New世代領域とSurvivor領域の比率(Eden領域/From領域) |
-XX:TargetSurvivorRatio | New世代領域GC後のFrom領域内オブジェクトの割合目標 |
32bit OSを使う場合など、使用するOSによっては、ヒープ・サイズに制限があります。初期サイズと最大サイズが制限されるので、32bit OSを利用する際には、注意が必要です。
- 32bitOS: 1.5Gから2Gの間
- 64bitOS: (ほぼ)無制限
本番稼働時には、初期値と最大値に同じ値を設定します。JavaVMが動的にサイズを拡大する処理は、パフォーマンス的によくないからです。一方、ヒープ・サイズの最適値を探す段階では、それぞれ異なる値を設定して動作させます。
NewSizeの値は、ヒープ・サイズの25%~50%の間で設定します。寿命の短いオブジェクトが多い場合には、できる限りガベージ・コレクションを起こさないためにも、New世代領域を多めに設定するとよいでしょう。
アプリケーションの性能が思わしくない場合、アプリケーションを動作させながら、ヒープ・サイズの測定と、YGC、FullGCの回数を、こまめに確認します。これにより、大抵の問題を検出する足がかりになります。非常に大切な作業なので、忘れないようにしましょう。
フレームワークやライブラリを使う際にも、監視が重要になります。これらは設計や実装の工数を軽減するために有益ですが、思いもよらないメモリー消費を検出することも少なくありません。使いこなされたライブラリの場合はメモリー消費を抑える改修をしていることもあるので、仕様をきちんと押さえておきましょう。
以上、Javaアプリケーションのサイジングとチューニングの概要について、解説してきました。現在は、仮想化環境に移行しているシステムも出始め、簡単にスケール・アウトできる環境になっていますが、環境が変わっても、可能な限りチューニングすることが大切です。