GCを適切に行わせるためのヒープサイズの設定
JVMにGCを適切に行わせるにはヒープサイズを適切に設定(New領域サイズ、Old領域サイズ、領域サイズのバランスなど)する必要があります。当然、適切なヒープサイズはアプリケーションに依存します。一般にヒープサイズが小さいとGCが頻発してアプリケーションのパフォーマンスが低下します。さらに、ヒープサイズが必要量を下回る場合はOutOfMemoryErrorが発生してアプリケーションが停止してしまいます。一方、ヒープサイズが大きいと、GCの起動回数は減りますが、GC1回当たりの処理時間、すなわちアプリケーション停止状態が長くなり、アプリケーションの応答時間に問題が出る場合もあります。システムの物理メモリのフリー領域が不足するまでヒープサイズを大きくすると、物理メモリからスワップ領域へのページングが起こってしまい、かなりのパフォーマンスが劣化する可能性もあります。通常、ヒープサイズは実メモリより大きく取ることはありません。
New世代領域(Eden領域、From領域[=To領域])、Old世代領域の各サイズはJVMのオプションで指定できます。
ヒープサイズオプション | サイズ |
---|---|
-Xms | 初期ヒープサイズ(全体) |
-Xmx | 最大ヒープサイズ(全体) |
-Xmn(-XX:NewSize) | New世代領域サイズ |
-XX:MaxNewSize | New世代領域サイズ |
-XX:NewRatio | New世代領域とOld世代領域の比率(Old世代領域/New世代領域) |
-XX:SurvivorRatio | New世代領域とSurvivor領域の比率(Eden領域/From領域) |
-XX:TargetSurvivorRatio | New世代領域GC後のFrom領域内オブジェクトの割合目標 |
-Xms、-Xmx(-Xms≦-Xmx)に異なる値を設定すると、ヒープサイズは起動時には-Xmsで指定された大きさですが、状況によってJVMがヒープが足りないと判断した場合は最大-Xmxで指定した大きさまで拡大します。これらの値を同じにするとヒープサイズ調整のオーバーヘッドがなくなりパフォーマンスが上がる場合もあります。
GCの監視
GCアルゴリズムから分かるように、短命オブジェクトに対してはNew世代領域で対処し、長命オブジェクトのみをOld世代領域に割り当てることによりGCの回数やGCにかかる時間が最も効率的になります。そのようにヒープサイズを調節することがパフォーマンスの向上につながります。基本的にはアプリケーションの短命オブジェクト、長命オブジェクトのサイズを見積もり、それに応じたヒープサイズを設定すればよいのですが、見積もりが容易でない場合があります。その場合は実際にアプリケーションを実行して、パフォーマンスを計測すると同時にGC回数、GC時間やヒープ内オブジェクト量などの状況を見ながらサイズを決めていくことができます。
実行時のヒープ内のオブジェクト情報を得るにはいくつか手段があります。ヒープ全体サイズやフリーな領域のサイズを知るためにはアプリケーション内でjava.lang.RuntimeクラスのtotalMemory()メソッド(ヒープ全体のサイズ)や、freeMemory()(ヒープ内のフリーな領域のサイズ)を利用できます。また、アプリケーション実行時にリアルタイムにオブジェクトの様子をグラフ化するツールなどもあります(アプリケーションサーバBEA Weblogic Serverなどではアプリケーションサーバ管理コンソール画面でヒープ内のオブジェクトの変化をグラフで見ることもできます)。
また、アプリケーション起動時に-Xrunhprof(JVMPI:Java Virtual Machine Profile Interfaceを使ったhprofエージェント)オプションを使ってJVMを実行すると、JVMにシグナルを送ることによってその時点のヒープ内オブジェクトのスナップショットを得ることもできます(JVMPIはJVMに実験的に実装されています。次期メジャーリリースのJVM 1.5ではJVMを監視するための新しい標準APIの実装が計画されています)。
詳細な分析をもってチューニングを行うためには、以下に述べるようにJVMにGC情報をログファイルにいったん出力させて、アプリケーション実行終了後にログファイルを基に分析を行うのがよいでしょう。
GCに特化した情報を得るにはJVMにはGC情報をログとして出力させるオプションが各種あります。知りたいGC情報の内容に従ってオプションを設定します。以下の例は結果をログファイルに出力しています。
%java -Xloggc:output_file <Javaアプリケーションファイル>
..... 12.871: [GC 17728K->12682K(18112K), 0.0035231 secs] 12.875: [Full GC 12682K->10762K(18112K), 0.1355816 secs] ....
出力の1行にGC1回に関する情報が対応します。上の例では、GC2回分の情報を表示しています。この出力から、Javaアプリケーションの起動から12.871秒後にNew領域のみでGCが行われ(単にGCで表示)、ヒープ内の全体のオブジェクトが17728Kから12682K に減っていることが分かります。( )内の18112Kはそのときの全体のヒープサイズを表しています。最後項目の0.0035231secsはこのGCにかかった時間(この間アプリケーショ-ンは停止)を表しています。2 回目はNew領域、Old領域どちらに対してもGCが起こっている(Full GCで表示)ことが分かります。- XX:+PrintGCDetails オプションでは、New世代領域、Old 世代領域それぞれについてのオブジェクト量の変化情報も得られます。HP-UXのJVMでは-Xverbosegcを指定することにより、さらに詳細なGCの種類、GCが起きた理由なども出力されます。
以下にGCの監視に役立つ主なオプションを示しておきます(オプションのサポートに関しては出力フォーマットも含めて変更になる可能性があるので注意してください)。
主なGC監視オプション | 出力情報 |
---|---|
-verbose:gc(-verbosegc) | GC情報(簡易) |
-Xloggc:filename | GC情報(ファイルへ出力) |
-XX:+PrintGCDetails | GC情報(詳細) |
-Xverbosegc | GC情報(詳細)を出力(HP-UX JVMのみ) |
-XX:+PrintTenuringDistribution | オブジェクトの年齢情報 |
-XX:+TraceGen0Time | New世代領域の累積GC時間、GC回数、平均GC時間 |
-XX:+TraceGen1Time | Old世代領域の累積GC時間、GC回数、平均GC時間 |
-XX:+PrintGCTimeStamps | GCのタイムスタンプ |
-XX:+PrintHeapAtGC | GC前後の詳細なヒープ情報 |
出力はすべてテキストファイルなので直接読めるのですが、出力された数字の羅列を直接見てもGCの様子を把握するのは難しいことです。ツールを利用することにより、出力データのサマリやメトリクスのグラフ化を行うことができ、GC分析をより効率よく行えます。フリーのツールとしてHPjtuneやGCViewerなどがあるので、これらを利用するのがよいでしょう。
アプリケーションのパフォーマンス(スループットやレスポンスタイム)を計測すると同時に、これらのオプションやツールを使いGCの回数、GCにかかった時間、GCの種類など分析して、ヒープのサイズが適切であるかどうかを判断し調節することができます。
大規模システムに向けたGC
以上が標準(デフォルト)のGCの話でしたが、JVM 1.4.1からは大規模なシステム(大メモリ、多CPU)に対してスループット向上とGCによる停止時間減少を目的とした新たなGCも実装しています。ただし、現時点ではまだ実行環境状況によっては安定した動作をしない場合もあるようなので注意が必要です。
パラレルGC(JVMオプション: -XX:+UseParallelGC、
-XX:+UseParNewGC)
標準のGCではNew世代領域に対してシングルスレッドでGCを行っていましたが、パラレルGCでは複数スレッドを使って同時に行います。その結果として、マルチCPU環境では、GC時間の減少、スループットの向上の効果が期待できます。パラレルGCを行うスレッド数は-XX:ParallelGCThreads=<スレッド数>(デフォルトはCPU数)で指定できます。-XX:+UseParallelGCと-XX:+UseParNewGC では同様の処理なのですが、一緒に利用できるオプション制限が異なります。
例えば、下のコンカレントGCと一緒に利用できるのは、-XX:+UseParNewGCの方だけです。2つのオプションのどちらを使うかは、アプリケーションの実行時に実際に試して比較するのがよいでしょう。
また、JVMには-XX:AggressiveHeapというオプションもあり、システムに応じて自動的に大規模なアプリケーションに適した設定を行ってくれます。ヒープサイズを実メモリに応じて大きく設定したり、ほかにもいくつかのオプションが自動的に設定されるのですが、SDK 1.4.2ではGCの種類については- XX:UseParallelGCが設定されます。-XX:AggressiveHeapは取りあえず、大規模用のサーバなどで試すのによいかもしれませんが、JVMのバージョンによって挙動は異なる可能性があります。
コンカレントGC(JVMオプション:-XX:+UseConcMarkSweepGC)
標準のGCではOld世代領域のGC期間中ずっとアプリケーションスレッドは停止していましたが、このGCではGC処理の多くをアプリケーションと並行に処理されます。従って一部の処理のときだけアプリケーションスレッドを停止するため、アプリケーションの停止時間を短くできます(Old領域内のオブジェクトに対しては必要になるまでコンパクト化は行われません)。
本来GCは人がメモリ管理を意識しない間に適切に動作することが理想的です。GCは常に進化している技術であり、将来的にはアプリケーションに応じてヒープサイズの調節や、適切なGCの選択をJVMが自動的に行うようになるかもしれません。ただし、現時点ではGCのアルゴリズムを理解し、アプリケーションの動作中GCの様子を観察しながら、適切なGC選択、ヒープサイズの調整を行うことが、安定したパフォーマンスの良いアプリケーション作成の手助けとなるでしょう。
筆者紹介
日本HP コンサルティング・インテグレーション統括本部
ITコンサルティング本部 テクノロジーコンサルティング部
猪口聖司(いのくち せいじ)
横河・ヒューレット・パッカード(株)(現日本ヒューレット・パッカード(株))に入社後、HP-UX上の日本語環境ソフトウェアの開発に従事。(株)国際電気通信基礎技術研究所(ATR)へ出向し、人とシステムのインタラクションに関する研究を行う。その後、2000年問題対応のため品質管理を経験し、現在HP-UX技術コンサルティングに従事。デバイスドライバ開発サポートや、Javaプラットフォームパフォーマンスチューニングを中心に活動中。共訳書に「JMF(Java Media Framework)」(ピアソン・エデュケーション)がある。 また、日本ヒューレット・パッカードが提供している技術情報サイト「HP-UX Developer Edge」の執筆を担当している。
Copyright © ITmedia, Inc. All Rights Reserved.