何がアプリケーションのパフォーマンス上の問題の原因となっているのか、アプリケーションのパフォーマンスに関するエキスパートにならなくても問題を修正するにはどうすればよいのか、アプリケーションは安定しているのだろうか、アプリケーションの構成は適切なのだろうかなど、アプリケーションのパフォーマンスに関する疑問はさまざまにあります。このような疑問の答えとなるよう意図されているのが、IBM の新しいツール、IBM Monitoring and Diagnostic Tools for Java - Health Center です。Health Center はガーベッジ・コレクションのアクティビティー、メソッドの実行、アプリケーションの同期、そして構成をチェックするだけでなく、Health Center 内部のエキスパート・システムが問題を診断するために必要な情報を提供し、問題を解決に導いてくれます。つまりエキスパート・システムでは、問題の分析を行い、懸念される問題領域について警告をし、推奨される解決方法を提示し、実行するとよいコマンド行を示してくれます。しかも Health Center は本番環境でも使用できるほど軽量です。この記事では、Health Center をダウンロードしてインストールする方法から、実際にこのツールを使用してアプリケーションのトラブルシューティングを行う方法まで説明します。
Health Center ツールは、クライアントとエージェントの 2 つで構成されます。エージェントは監視対象の JVM からクライアントに情報を送信します。クライアントはエージェントに接続して、実行中の Java アプリケーションの正常性に関する情報を GUI に表示します。
Health Center クライアントは、ISA (IBM Support Assistant) の一部となっています。クライアントをインストールするには、以下の手順に従ってください。
- ISA Workbench をダウンロードしてインストールします。
- ISA Workbenchを起動し、メニュー・バーから「Update (更新)」 > 「Find New... (新規検索…)」 > 「Tools Add-ons (ツール・アドオン)」の順に選択します。
- 「Find new tools add-ons (新規ツール・アドオンの検索)」ウィザードで、検索ボックス (フィルター・テキストの入力ボックス) に「
health」と入力してから「JVM-based Tools (JVM ベースのツール)」の横にある Twistie (折り畳みアイコン) を展開し、Health Center エントリーを表示します (図 1 を参照)。
図 1. Health Center クライアントを ISA にインストールする
- Health Center のエントリーを選択して「Next (次へ)」をクリックし、その後はプロンプトに従います。インストールが完了したら、ISA を再起動します。
クライアントのインストールが完了したら、クライアント内からエージェントをダウンロードしてインストールします。
- ISA の「Welcome (ようこそ)」ページで「Analyze Problem (問題分析)」をクリックします。
- 「Tools (ツール)」サブタブを選択します。インストール済みツールのリストから「IBM Monitoring and Diagnostic Tools for Java - Health Center」を選択し、「Launch (起動)」をクリックします。これによって、「Health Center Connection wizard (Health Center: 接続ウィザード)」が開きます (図 2 を参照)。
図 2. 接続ウィザード
- 「Enabling the application for monitoring (アプリケーションのモニターの有効化)」リンクをクリックします。
- 次に表示されたページで、「Installing the Health Center agent into an IBM JVM (IBM JVM への Health Center エージェントのインストール)」をクリックします。
- 「Installing the Health Center agent into an IBM JVM (IBM JVM への Health Center エージェントのインストール)」ページでは、お使いのシステム上の JVM に対応するリンクをクリックし、エージェント・ファイルの圧縮アーカイブをダウンロードします。
- ダウンロードしたアーカイブを、監視対象の JVM のルート・ディレクトリーに解凍します。Java 6 Service Refresh 2 および Java 5 Service Refresh 8 からは、Health Center エージェントはすでに JVM に組み込まれています。しかし、組み込まれているエージェントが最新バージョンではない可能性もあるので、新しいエージェントで既存の Health Center エージェントを上書きすることが最善の策です。インストール中にファイルを上書きするかどうか選択するよう求められた場合には、「
Yes (はい)」を選択してください。
アプリケーションを Health Center で監視するには、Health Center エージェントを有効にするためのコマンドライン・オプションを指定してアプリケーションを起動する必要があります。オプションの構文は、お使いの JVM バージョンによって以下のように異なります。
- Java 6 SR5 以降では、
-Xhealthcenterを指定します。 - Java 6 SR1 以降では、
-agentlib:healthcenter -Xtrace:output=perfmon.outオプションを指定します。 - Java 5 SR10 以降では、
-Xhealthcenterを指定します。これ以外の場合には、-agentlib:healthcenter -Xtrace:output=perfmon.outを指定します。
お使いの Java のバージョンがわからない場合には、java -version コマンドを実行すると、バージョン情報がコンソールに出力されます (図 3 を参照)。
図 3. Java のバージョンを調べる
どのコマンド・オプションを指定してエージェントを有効にすればよいのかを簡単に判別する方法は、まず -Xhealthcenter オプションを試してみることです。このオプションで上手く行かなければ、次に -agentlib:healthcenter -Xtrace:output=perfmon.out を試してみてください。
アプリケーションを起動したときに Health Center エージェントが正常に起動されると、コンソールにメッセージが出力されます。図 4 に一例として、-Xhealthcenter オプションを指定して java -version を実行し、Health Center エージェントがポート 1972 で起動された場合の出力を示します。通常、エージェントはポート 1972 でリッスンしますが、例えば別の Health Center エージェントによってポート 1972 がすでに使用されている場合には、ポート番号が自動的にインクリメントされます。
図 4.
java -version を実行して有効になった Health Center
エージェントがいったん起動すれば、後は Health Center クライアントを使用してアプリケーションを監視し、パフォーマンス上および構成上のさまざまな問題を解決することができます。
パフォーマンスの最適化は、具体的な証拠に基づいて行わなければなりません。手軽な方法として、アプリケーションのコードを調べた後、修正を適用してパフォーマンスが改善されるかどうかを見てみるという方法に走りがちですが、早まった最適化は逆効果になり得るだけでなく、危険も伴います。最適化する前のコードは、速度は遅いながらも自然なコードになっていますが、最適化したコードは、最適化する前のコードと比べて保守が難しく、コードも不自然な形になりがちです。また、このような最適化の方法には時間もかかります。パフォーマンスが改善されれば、最適化にかかるコストと大々的に最適化されたコードの保守の難しさを考えても明らかに最適化する価値はありますが、多くの最適化はパフォーマンスの改善をほとんどもたらしません。最適化は、それによって違いがもたらされる領域を対象に行わなければなりません。最適化によって違いがもたらされる領域を識別するプロセスとは、すなわちボトルネックを見つけるプロセスです。
基本的に、パフォーマンス上の問題のすべての原因は、リソースが制限されていることです。パフォーマンスに影響を与える基本的な計算リソースには、CPU、メモリー、I/O、ロックがあります。例えば CPU バウンドのアプリケーションは、要求されている作業を完了するのに十分なプロセッサー時間を獲得できない場合があります。メモリー・バウンドのアプリケーションは、使用可能なメモリー以上のメモリーが必要であったり、I/O バウンドのアプリケーションは、システムで処理しきれないほどの速さで I/O を処理しようしていたりすることがあります。また、ロック・バウンドのアプリケーションでは、複数のスレッドが同じロックを奪い合おうとします。この場合、スレッド間の同期がロック競合を引き起こしており、システムがますます並列化されるようになってきていることから、同期がスケーラビリティーにおける制限要因となっています。
どのリソースが不足しているのかを判別するには、ボトルネックを見つけることです。Health Center には、アプリケーションのパフォーマンスが良くない原因を調査し、診断するためのさまざまな機能が備わっています。パフォーマンスの分析とは、ボトルネックを見つけ出して修正し、さらに次のボトルネックを見つけて修正するというプロセスで、このプロセスはアプリケーションのパフォーマンスが満足のいくものになるまで繰り返されます。Health Center は、どのリソースが不足しているのかを特定するために必要なプロセスの多くを自動化します。現在、I/O 使用率の分析には対応していませんが、CPU 使用率、メモリー使用率、ロック使用率については可視化および対策を推奨することが可能です。
Health Center の「Status (状況)」パースペクティブに表示されるダッシュボードには、不足する可能性のある各リソースのステータス・インジケーターがあります。赤またはオレンジ色のステータスが意味するのは、その領域では、アプリケーションのパフォーマンスを改善可能だということです。図 5 に、「Status (状況)」パースペクティブ」が開かれている状態を示します。
図 5. 「Status (状況)」パースペクティブ
過度のガーベッジ・コレクション (GC) は、アプリケーションのパフォーマンスの低下を引き起こす一般的な原因です。GC とは JVM がアプリケーションのメモリーを自動的に管理するために使用するプロセスで、コードの安全性および簡潔さの点、そして多くの場合パフォーマンスの点でも GC には多大なメリットがあります。その一方で、ガーベッジ・コレクターは処理を行うためのリソースを使用するのも確かです。そしてアプリケーションでのメモリー使用量の異常なパターンや不適切に構成されたヒープ・サイズによっても、ガーベッジ・コレクターがパフォーマンス上の重大なボトルネックの原因となる可能性はあります。
Health Center は、GC のアクティビティーの詳細を視覚化し、GC のチューニングに関する推奨案を提供します。図 6 に、Health Center の「Garbage Collection (ガーベッジ・コレクション)」パースペクティブを示します。このパースペクティブには、使用されたヒープのグラフ (アプリケーションのメモリー使用率を評価するのに役立ちます) と GC による一時停止時間のグラフ (GC によるパフォーマンスへの影響を評価するのに役立ちます) が表示されます。また、GC 統計を要約した表と一連の推奨案、そして必要な場合には推奨コマンド行も提供されます。
図 6. 「Garbage Collection (ガーベッジ・コレクション)」パースペクティブ
図の全体を大きく表示するには、ここをクリックしてください。
GC によるパフォーマンスへの影響を評価する際に最も重要となるチェック項目は、GC による一時停止時間とオーバーヘッドです。つまり、アプリケーションの作業ではなく、ガーベッジ・コレクションに費やされた時間の割合に注目しなければなりません。図 7 に、GC によるオーバーヘッドに関して Health Center が計算した結果を示します。オーバーヘッドが大きければ、アプリケーションに十分な処理時間が与えられていないことになります。一時停止時間が長ければ、アプリケーションの応答性が低下する可能性があります。GC による一時停止の間は、アプリケーションのアクティビティーが停止するためです。
図 7. GC によるオーバーヘッドを示した表
GC によるパフォーマンスへの影響を抑える手段はいくつかあります。まず 1 つは、生成するガーベッジを減らすことです。アプリケーションがオブジェクトを作成した後すぐにオブジェクトを破棄していないか、ストリング・バッファーを使わずにストリングを繰り返し連結していないか、autoboxing によって表面化で不要なプリミティブ・ラッパーが作成されている可能性はないかどうか、コードが不必要に大きなオブジェクトを作成していないかどうかを調べてください。また、パフォーマンスを改善する手段として、ヒープ・サイズを増やしたり、新しい世代領域のヒープ・サイズをいじってみたり (世代別コレクターの場合)、または GC ポリシーを変えてみたりすることもできます。ただし、いずれの手段をとるにしても慎重に行う必要があります。
例えば、GC を改善するためにコードをチューニングするときには注意が必要です。一見すると最適化のように思えても、実はパフォーマンスをさらに劣化させる結果になることがあります。その一例として、オブジェクトを繰り返し作成しなくても済むように、既存のオブジェクト・インスタンスを再利用するという方法を思い付きがちですが、オブジェクト・プールを安全に管理するには、同期というオーバーヘッドが伴います。プール・サイズが正しくチューニングされていなければ、多くのオブジェクト・インスタンスが使用されないまま、無駄にメモリーを使い果たす結果にもなり得ます。世代別ガーベッジ・コレクター (gencon ポリシーで使用されるようなガーベッジ・コレクター) ではそれよりもさらに深刻な事態として、存続時間の短いオブジェクトは実質的にコストをかけずに収集されるものの、長時間存続するオブジェクトのコストが非常に高くなります。オブジェクトを繰り返し作成して破棄しても、それに伴うパフォーマンスのオーバーヘッドはわずかですが、既存のオブジェクト・インスタンスを再利用できるように保持するとなると、ガーベッジ・コレクターにとって極めて大きな負担となります。
もう 1 つの GC チューニングの罠は、目の前に示された GC によるオーバーヘッドの数値 (図 7 を参照) を減らすことに集中し過ぎて、全体的な目標がアプリケーションのパフォーマンスの最適化であることを忘れてしまうことです。GC によるオーバーヘッドを減らせば一般的にアプリケーションのパフォーマンスは向上しますが、これには例外もあります。例えば、gencon ポリシーを使用した GC にかかる時間は、optthruput ポリシーを使用してガーベッジを収集する場合より長くなるのが通常です (「参考文献」を参照)。しかし gencon ポリシーによってヒープ内のオブジェクトを配置した方が、オブジェクトを割り当てる際にも、オブジェクトへアクセスする際にも遙かに時間を短縮することができます。アプリケーションは一般に、オブジェクトの割り当てとオブジェクトへのアクセスに多くの時間を費やすため、多くのアプリケーションでは、こうした動作 (つまり、GC によるオーバーヘッドには含まれない動作) におけるパフォーマンスの向上が、GC そのものによって発生するコストを上回ります。さらに驚くことに、optavgpause ポリシーは GC による処理のほとんどすべてを並行して行うため、STW (Stop-The-World) 型による一時停止はほんの一瞬でしかありません。そのため GC によるオーバーヘッドは極めて小さくなり (通常は 1 パーセント未満)、優れたアプリケーションの応答性がもたらされます。その一方、アプリケーションのスループットの点では、optthruput や gencon ポリシーほどは優れてはいません。
ロック競合が発生するのは、ロックが使用されているときに別のスレッドがロックを獲得しようとした場合です。つまり、頻繁に取得されるロックや長時間保持されるロック、あるいはその両方の条件を持つロックでは競合が発生しやすいということです。競合しやすいロック (多数のスレッドがアクセスしようとするロック) はシステム内のボトルネックになりかねません。実行中のスレッドのそれぞれが、必要とするロックを獲得するまで処理の実行を中断し、それによってアプリケーションのパフォーマンスが制限されてしまうためです。システム内のスレッドの数が多ければ多いほど、スレッドが有益な作業をする代わりにロックを待機する時間の割合のほうが大きくなることから、同期はとりわけアプリケーションのスケーラビリティーの妨げになりがちです。Health Center はロック・アクティビティーを視覚化し、リクエストをブロックしているロック、そしてパフォーマンスに影響を与える可能性のあるロックを明らかにします。
図 8 に、Health Center の「Locking (ロック)」パースペクティブを示します。
図 8. 「Locking (ロック)」パースペクティブ
図の全体を大きく表示するには、ここをクリックしてください。
図 8 に示されたグラフの内容を理解するには、表示されているデータについて理解する必要があります。グラフの棒は、競合が発生する度合いに応じて緑から赤に色分けされています。頻繁に競合が発生するロックは明るい赤、競合が発生しないロックは明るい緑で示されます。棒の高さは、どのロックが最もリクエストをブロックしたかを相対的に表しています。したがって、色と高さを合わせると、競合が最も発生しやすいロックがわかるというわけです。
ロックの棒はかなり高くなっていながらも、色は緑のままという場合があります。これは、ロックによってブロックされたリクエストの数は多い一方、リクエストがロックの獲得に成功した回数が多ければ、ブロックされたリクエスト全体のパーセンテージは低くなるためです。けれども、このようなロックによってパフォーマンスに影響が及ぼされる可能性がないわけではありません。例えば、図 9 のロックのグラフにはオレンジ色に近い色の 2 つの高い棒が示されています。
図 9. ロックのグラフ
図 9 では、棒の高さによって競合が発生しやすいことを示しているこの 2 つのロックが、詳しい調査対象の第一候補となります。ロックのなかでとりわけ高い棒をもつこの 2 つは、ブロックされたリクエスト数が最も多いことを示しているだけでなく、色も赤に近い方の色になっています。つまり、これらのロックによってブロックされたリクエスト全体のパーセンテージが高いということです。
Slow カウントの高いロックはすべて、パフォーマンスに影響を与える可能性が考えられます。このようなロックよるパフォーマンスへの影響を軽減するにはどうすればよいのでしょうか。そのための最初のステップは、どのコードが問題のあるロックを使用しているかを見極めることです。Health Center にはロックのクラスが示されています。場合によっては、ロックのクラスを見るだけで該当するロックを識別できることもありますが、それ以外の場合はロックのクラスだけでは識別し切れません。例えば、ロック・オブジェクトのクラスが Object だとすると、コードを調べてこのオブジェクトへの参照を見つけるのは容易なことではないかもしれません。図 10 に、DataStore クラスと Object クラスのさまざまなロックを対象とした Health Center によるロック・アクティビティーの分析を示します。Health Center ではロックをパフォーマンス上の問題として示しているけれども、Health Center による分析からは問題の原因となっているロックがどれであるかがわからない場合には、ロック・オブジェクトの中でそれぞれを区別しやすいクラス名を使用するようにコードをリファクタリングするか、一連の javacore を取得して、最も頻繁に使用されるロックを呼び出している呼び出しスタックを捕捉するという方法があります。
図 10. ロックの表
問題のあるロックを特定できたら、ロック競合が軽減されるようにアプリケーションを作成し直さなければなりません。競合を最小限に抑えるには、2 つの方法があります。1 つはロックを保持する時間を短縮して、競合が発生する可能性を減らすという方法です。もう 1 つの方法では使用するロック・オブジェクトの数を増やし、それによってそれぞれのロック・オブジェクトが使用される回数を減らします (「参考文献」を参照)。
メソッド・プロファイルを見れば、アプリケーションがどのコードの実行に時間を費やしているのかがわかります (アプリケーションがコードの実行ではなくロックの待機に費やしている時間や、JVM がコードの実行ではなくガーベッジの収集に費やしている時間はわかりません)。1 つか 2 つのメソッドばかりが CPU 時間を過度に使用している場合には、これらのメソッドを最適化することで、パフォーマンスを大幅に向上させられる可能性があります。図 11 に、Health Center の「Profiling (プロファイル作成)」パースペクティブを示します。このパースペクティブには、最もアクティブなメソッドをアクティビティー・レベル順 (頻繁に使用される順) にソートした表、そして一連の推奨案が示されます。
図 11. 「Profiling (プロファイル作成)」パースペクティブ
図の全体を大きく表示するには、ここをクリックしてください。
オレンジまたは赤で表示されたメソッドがなければ、アプリケーションによるそれぞれのメソッドの実行時間は比較的均等にバランスが取れていることになります。しかしそれでも、最も頻繁に使用されるメソッドについては最適化する価値はあります。そのようなメソッドは、低いパフォーマンスしかもたらさない可能性があるからです。例えば、あるメソッドが I/O を過度に使用しているという場合があります。この場合、1 つのメソッドが他のメソッドよりも多く CPU を使用していることは明らかなので、そのメソッドは赤で表示されます。図 11 にある表の左の方の「Self (自己)」列の値を見ると、JVM は時間の 42.1 パーセントをアプリケーションの実行内容のチェックに費やし、DataStore.storeData(I) メソッドを実行していることがわかります。その右側にある「Tree (ツリー)」列には、アプリケーションが DataStore.storeData(I) メソッドとその派生メソッドの両方にどの程度の時間を費やしているのかが表示されます。単純なシングル・スレッドのアプリケーションでは、「Tree (ツリー)」時間の 100 パーセントは main() メソッドで費やされることになります。
パフォーマンスのチューニングをする目的は、アプリケーションが行う作業を減らすことです。アプリケーションのほとんどの作業は、メソッド・プロファイルの上の方に示されているメソッドで行われるため、検討する対象はこれらのメソッドとなります。メソッド・プロファイルの下の方に表示されているメソッドは無視してもまったく問題ありません。
メソッド・プロファイルの先頭付近に挙がってくるメソッドには、次の 2 種類があります。1 つは頻繁に呼び出されているメソッドで、もう 1 つは呼び出されると大量の処理を行っているメソッドです。メソッドがループを使用する場合、特にネストされたループを使用する場合には、そのメソッドの実行時間は長くなります。多数のコード行が含まれるメソッドもまた、非常に短いメソッドより実行時間が長くなりがちです。コードの量はそれほど多くなく、ループを使用しないメソッドがプロファイルの先頭にある場合、それは、かなり頻繁に呼び出されているメソッドであると考えられます。
実行時間の長いメソッドでアプリケーションが行う作業量を減らすこと、そして頻繁に呼び出されるメソッドの呼び出し回数を減らすことによって、アプリケーションの最適化を実現することができます。メソッド内での作業を減らすのに最も効果的な方法は、できるだけ多くのコードをループの外に移すことです。また、メソッドの呼び出し回数を減らすには、そのメソッドを呼び出しているコードを見つけて、メソッドを呼び出さないようにします。大抵の場合、少なくとも最適化の最初の何回かは、メソッド・プロファイルの先頭に挙がっているメソッドの呼び出しのうち、まったく不要であって明らかに削除できるものが驚くほどの割合を占めていることがわかるはずです。メソッドの呼び出しパスを調べるには、Health Center の「Method profile (メソッド・プロファイル)」内でメソッドを選択してください。それによって、画面の下端にそのメソッドの呼び出しパスが示されます。図 12 には、DataStore.storeData メソッド呼び出しの 90.3 パーセントが、StoreData.run メソッドからの呼び出しであったことが示されています。
図 12. 呼び出しパスのツリー
例えばガーベッジ・コレクションのアクティビティーが突然活発になったり、ロックの競合が頻繁に発生したり、あるいはあるメソッドがメソッド・プロファイルの先頭に躍り出たりするなど、特定の期間中にアプリケーションの振る舞いが大幅に変わることがあります。Health Center では、選択した期間のみを対象として分析を行うことが可能です。グラフ上の四角形をドラッグすれば、表示される期間を絞り込むことができます。絞り込まれた期間は、すべてのパースペクティブに反映されます。推奨案は選択された期間のみに基づいた推奨案に更新され、メソッド・プロファイルの表も選択された期間中のみのサンプル数とパーセンテージに更新されます。こうすることにより、例えば GC が正常に機能していなかった期間に実行されたメソッドを調べたり、アプリケーションの起動時に分析されたデータを排除したりすることができます。
最近の環境では、1 つの Java アプリケーションが作成されて、そのアプリケーションが複数の個人、場合によっては複数の大きなチームによってデプロイされることがよくあります。これは、アプリケーションがアプリケーション・サーバーやフレームワークのなかで実行されている場合には特に当てはまることです。この場合、起動パラメーターは複数の異なる場所で設定可能であるため、どの JVM がどのアプリケーションをどの構成で実行しているのかを突き止めるのは、極めて困難になる可能性があります。普通の状況であれば、このような環境でも問題ないかもしれませんが、問題が発生した場合には、Java アプリケーションがどのように構成されているのかを十分に理解していることが必要不可欠です。不適切な構成によって引き起こされるパフォーマンス上の問題は、これまで説明した手法で特定できるとは限りません。また、不適切な構成はアプリケーションの予期せぬ振る舞いの原因となり、アプリケーションによるサービスの提供ができなくなる恐れがあります。
Health Center には、「Environment (環境)」というパースペクティブが組み込まれており、このパースペクティブから、監視対象アプリケーションのクラス・パス、システム・プロパティー、環境変数がわかります。さらにこのパースペクティブは Java の構成を分析し、潜在的な問題に関する推奨案を提供します。図 13 に、「Environment (環境)」パースペクティブを示します。
図 13. 「Environment (環境)」パースペクティブ
図の全体を大きく表示するには、ここをクリックしてください。
「Environment (環境)」パースペクティブに示される情報のうち、最も単純な情報として挙げられるのは、監視対象の JVM がインストールされている場所に関する情報です。意外なほどに、この情報だけで問題を解決できることがよくあります。必要なクラスが使用不可能か、パフォーマンス特性が期待されるものとは異なっているか、JAVA_HOME でのファイルの変更 (ロギングやセキュリティー構成、またはライブラリーの変更など) がまだ反映されていないか、といった問題を診断する際に最初にチェックすることは、実行中の JVM が、想定していた JVM であるかどうかです。最近のほとんどのシステムには多数の JVM がインストールされており、大規模なアプリケーションでは複数の場所に複数の異なる JVM が組み込まれていることさえあります。
Java 技術では、目眩がするほど広範なコマンドライン・オプションを使用することができます。オプションのなかには、使用するのに適した環境と、そうでない環境があるものもあります。最近のアプリケーションのデプロイメントでは、複数のネストされた起動スクリプトにオプションを設定できるため、アプリケーションが実際にどのオプションを使用して実行されているのか、常に明らかであるとは限りません。Health Center には最終的な Java コマンドラインのビューが用意されています。このビューを見れば、予期せぬアプリケーション構成を特定することができます。例えば、最大ヒープ・サイズが非常に小さい値に制限されているという場合があります。小さなユーティリティー・アプリケーションではそれでも問題ありませんが、メモリー内の大量のデータ・セットを操作するアプリケーションにとっては異常終了の原因となり得ます。異常終了してしまうアプリケーションの担当チームが、最大ヒープ・サイズが設定されていることを知らなければ、常にメモリーが不足している理由を理解することは困難です。図 14 に、Health Center で構成されているコマンドライン・オプションの一覧を示します。
図 14. コマンドライン・オプションの一覧
図の全体を大きく表示するには、ここをクリックしてください。
また、Health Center は Java 構成を分析し、望ましくない結果に至る可能性のあるオプションについて警告をします。例えば、デプロイメントのテスト・フェーズで追加されたオプションが、本番に移行する前に削除されていないことがあります。具体的には、テスト中に問題を検出するのに便利な -Xdebug や -Xcheck オプションなどのデバッグ・オプションです。これらのオプションはテストには役立ちますが、パフォーマンスのオーバーヘッドを伴うことから、最適なパフォーマンスが求められる場合には使用しないことが得策です。-Xnoclassgc などの他のオプションはパフォーマンスを多少向上させるとは言え、メモリー要件を制限なく増大させる危険があります。
すべての構成上の問題が Java コマンドラインに関連するというわけではありません。ベースとなるシステム・プロパティーが原因で構成上の問題が発生する場合もあります。そのような構成上の問題のなかでも最も顕著な問題は、プロセスで使用するリソースに対して Linux® および AIX® システムが設定している制限に関連します。CPU 使用率、仮想メモリーの使用量、ファイル・サイズ、コア・ファイルのサイズは、ulimit コマンドによって制限することができます。制限を設定すると役立つ場合はあるものの、問題を診断する際の障害となる可能性もあります。ulimit があまりにも低く設定されているためにコア・ファイルが途中で切り捨てられたとしたら、そのファイルから有益な情報を引き出すことはほぼ不可能です。一部のシステムでは、デフォルトの ulimit の設定によってほとんど常にコア・ファイルが切り捨てられることになります (コア・ファイルとは、プロセスが異常終了した場合にオペレーティング・システムが自動的に作成する、そのプロセスのイメージのことです。異常終了をはじめ、アプリケーションにおけるさまざまなタイプの問題を診断する際に、このコア・ファイルは欠かせません)。
コア・ファイルは問題が発生したときにしか作成されないため、問題を引き起こす ulimit の設定が明らかになったときは、通常、後の祭りです。つまりその時点では、JVM は機能しなくなっているため、問題を診断するために必要なすべての情報が使用できなくなっています。一般に、ulimit によって完全なコア・ファイルの作成が妨げられないようにするための唯一の方法は、問題の可能性を推測して手動で ulimit の設定を調べることです。しかし残念ながら、かなり曖昧な情報を手掛かりとして、防護のために相当なシステム管理を行わなければならないため、理想的な方法であるとは言えません。Health Center は、サービスの提供ができなくなるという、このありがちな問題を自動的に検出します。Health Centerを使用すれば、ulimit の設定をわざわざ確認する必要はなくなります。ulimit に調整の必要があれば、警告を出してくれるからです。
異常終了は Java 環境ではめったにないことですが、Java アプリケーションが予期せず終了する可能性はあります。このような予期せぬ終了の原因はいくつもありますが、一般的な原因は、ネイティブ・コードとの JNI (Java Native Interface) を介して Java コードが呼び出され、ネイティブ・コードでの安全でないメモリー・アクセスによって一般保護違反 (GPF) がトリガーされることです。また、Java アプリケーションがヒープを使い切ったために実行を続けられないことが原因であることも珍しくありません。さらに Java アプリケーションがネイティブ・メモリーを使い果たしたために異常終了が発生することもあります (「参考文献」を参照)。メモリー (Java ヒープまたはネイティブ・メモリー) の枯渇が原因の異常終了は、OutOfMemoryException を特徴とします。
ほとんどの異常終了は予測できませんが、なかには例外もあります。具体的に言うと、Health Center は、Java ヒープの枯渇が引き金となる異常終了の予測を試みます。Health Center では、実行不可能なほど大きなオブジェクトをインスタンス化しようとした場合に発生する OutOfMemoryException は予測できませんが、大抵の OutOfMemoryException はメモリー・リークが原因で発生します。メモリー・リークとは、必要なくなったメモリーへの参照が保持されることです。このようなメモリーは、ガーベッジ・コレクターによって解放することはできません。不要なオブジェクトがヒープ内に蓄積されていくと、最終的には必要なオブジェクトのためのスペースがなくなって OutOfMemoryException がスローされます。図 15 は、メモリー・リークを起こしているアプリケーションが使用しているヒープを Health Center によって視覚化したものです。このアプリケーションのメモリー要件は常に増大しているため、Health Center は警告を表示し、最終的に異常終了になる可能性があることを説明しています。
図 15. メモリー・リークの疑いを示すグラフ
図の全体を大きく表示するには、ここをクリックしてください。
Heath Center によってリークが識別された場合には、どのように修正したらよいのでしょうか。修正の鍵となるのは、リークしているメモリーを使用しているオブジェクトを突き止めることです。そしてこのような分析を行うのに最も有効な方法とは、ヒープ・ダンプまたはシステム・ダンプを使用することです。ダンプには、ヒープ内のすべてのオブジェクト、それぞれのオブジェクトが使用しているメモリーの量、そしてそのオブジェクトを参照しているオブジェクトが記録されます。不要になったオブジェクトへの参照が保持されていることが、Java アプリケーションにおけるメモリー・リークの最も一般的な原因です。
ツールのサポートがなければ、ヒープを分析するのはほぼ不可能ですが、幸い、優れたツールはいくつもあります。Health Center はダンプを分析しないものの、この診断ツール・ファミリーの他のメンバーが分析を行います。Health Center と同じく、IBM Monitoring and Diagnostic Tools for Java - Memory Analyzer は ISA の一部として無料でダウンロードすることができます (「参考文献」を参照)。このツールは、リークの疑いがあるオブジェクトを特定した情報を含め、ヒープの内容に関する高度な要約情報を提供します。このツール単独で、リークの原因を突き止めて修正できる場合もよくあります。図 16 に、Memory Analyzer を実際に使用したときの画面を示します。
図 16. ISA の Memory Analyzer
図の全体を大きく表示するには、ここをクリックしてください。
複雑なリークの場合、Memory Analyzer にはオブジェクト照会言語を含め、ヒープのコンテンツをドリルダウンできる強力な機能も備わっています。
アプリケーションの実行、安定性、パフォーマンスに関する広範かつ有益な情報を提供する Health Center は、GC の使用に関する推奨、頻繁に呼び出されるメソッドの特定、ロック競合が発生するエリアの特定、そしてアプリケーションの実行環境に関する情報提供を行います。
Health Center は本番環境で問題が表面化する前に、パフォーマンス上の問題、メモリー・リーク、非効率的なコードを解決するのに役立つ貴重な開発ツールです。しかも、Health Center のオーバーヘッドは並外れて低いため、本番システムで問題を解決するためにも使用することができます。Health Center が提供する分析と推奨案があれば、問題を特定して修正するために Java 管理やパフォーマンス・チューニングのエキスパートになる必要はありません。
学ぶために
- 連載「Java の診断を IBM スタイルで」: Java アプリケーションでの問題の解決に役立つ IBM から入手可能なその他のツールについて学んでください。
- 「Install IBM Support Assistant and Health Center」および「How to enable a Java application for live monitoring by the Health Center」: この 2 つの動画で、ISA とクライアントおよび Health Center エージェントをインストールするための手順、そしてアプリケーションを監視できるようにする手順のすべてを見てください。
- Health Center 入門ガイド: Health Center のインストール手順については、このガイドで詳しく説明しています。
- IBM Monitoring and Diagnostic Tools for Java - Memory Analyzer: Memory Analyzer の詳細を学んでください。
- 「Java technology, IBM style: Garbage collection policies, Part 1」(Mattias Persson 著、developerWorks、2006年5月) および「Java technology, IBM style: Garbage collection policies, Part 2」(Mattias Persson、Holly Cummins 共著、developerWorks、2006年5月): IBM Developer Kit for the Java Platform で使用できる GC ポリシーと、ポリシーを選択する際に考慮すべき事項について学んでください。
- 「システム負荷を軽減したスレッド化: 競合を低減させる」(Brian Goetz 著、developerWorks、2001年9月): この記事では、ロック競合を最小限に抑え、アプリケーションのスケーラビリティーを改善するためのコードを作成する上での各種の技法を説明しています。
- 「Thanks for the memory」(Andrew Glover 著、developerWorks、2009年4月): この記事を読んで、ネイティブ・メモリーとは何か、Java ランタイムはネイティブ・メモリーをどのように使用するのか、ネイティブ・メモリーが不足するとどのような現象になるのか、そして Windows® と Linux ではネイティブ・メモリーの
OutOfMemoryErrorをどのようにデバッグすればよいのかを学んでください。関連記事では、AIX システの場合についての同じトピックを説明しています。 - Technology bookstore で、この記事で取り上げた技術やその他の技術に関する本を探してください。
- developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。
製品や技術を入手するために
- Health Center を含めた IBM Monitoring and Diagnostic Tools for Java の全ファミリーにアクセスする保守容易性ワークベンチ、IBM Support Assistant をダウンロードしてください。
- IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
- My developerWorks コミュニティーに加わってください。


