今まで知らなかった 5 つの事項: Java のパフォーマンス・モニタリング、第 2 回

JDK に組み込みのプロファイラーを使って Java プロセスをモニタリングする

JDK に JConsole というフル機能のプロファイラーが付属していることを今まで知らなかった人であれば、この記事で紹介するスタンドアロンの 5 つのプロファイリング・ユーティリティーについて知ると、さらに驚くことでしょう。スレッド不足、デッドロック、オブジェクトのリークといったパフォーマンスのボトルネックを解決する上で、Java™ プロセスのモニタリングや分析のための軽量な (そして場合によると試験段階の) ツールがどれほど役立つかを学びましょう。

Ted Neward, Principal, Neward & Associates

Ted Neward photoTed Neward は、Neward & Associates の代表として、Java や .NET、XML サービスなどのプラットフォームに関するコンサルティング、助言、指導、講演を行っています。彼はワシントン州シアトルの近郊に在住です。



2010年 7月 13日

この連載について

皆さんは自分が Java プログラミングについて知っていると思うかもしれません。しかし実際には、ほとんどの開発者は Java プラットフォームの表面的な部分しか扱っておらず、当面の作業を完了するために十分なことしか学んでいません。この連載では、Ted Neward が Java プラットフォームのコア機能を深く掘り下げ、非常に厄介な Java プログラミングの難題の解決にも役立つ、ほとんど知られていない事実を紹介します。

JConsole や VisualVM といったフル機能の組み込みプロファイラーを使用する場合、本番ハードウェア上で実行されるシステムの場合は特に、プロファイラーを使用するメリットよりもパフォーマンスのオーバーヘッドによる影響の方

が大きいことがあります。そこで、Java のパフォーマンス・モニタリングに焦点を当てる記事の第 2 回目である今回は、コマンドラインから使用する 5 つのプロファイリング・ツールを紹介します。開発者はこれらのツールを使用することで、実行中の Java プロセスの 1 つの側面のみに注目できるようになります。

JDK には多くのコマンドライン・ユーティリティーが含まれており、それらを使用することで、Java アプリケーションのパフォーマンスをモニタリングしたり管理したりすることができます。そうしたユーティリティーの大部分は「experimental (試験段階)」と記されており、従って本来はサポートされていませんが、それでも有用なツールです。中には、JVMTI や JDI を使って作成可能な特殊用途ツールの素材となり得るものもあります (「参考文献」を参照)。

1. jps (sun.tools.jps)

多くのコマンドライン・ツールでは、モニタリング対象の Java プロセスを特定する必要があります。これは、ネイティブ・オペレーティング・システムのプロセスをモニタリングする同様のツールにプロセス ID が必要なことと、あまり変わりがありません。

「VMID」という ID は、ネイティブ・オペレーティング・システムのプロセス ID ("pid") と必ずしも同じではありません。そのため、JDK の jps ユーティリティーが必要なのです。

Java プロセスの中で jps を使う

jps 実行可能プログラムは、JDK に同梱されている大部分のツールや、この記事で触れたすべてのツールと同じように、実際には Java クラス、すなわちほとんどの作業を実行する一連のクラスのシン・ラッパーです。Windows® の場合、これらのツールは .exe ファイルであり、JNI を呼び出す API を使って対象のクラスを直接呼び出します。UNIX® の場合、大部分のツールはシェル・スクリプトへのシンボリック・リンクであり、シェル・スクリプトは適切なクラス名が指定されると汎用の Java ランチャーを起動します。

jps (あるいは他の任意のツール) の機能を Java プロセス (例えば Ant スクリプト) の中から使用したい場合、その方法は比較的容易であり、各ツールの中心となるクラスの main() を呼び出すだけでよいのです。わかりやすいように、そのクラスの名前を各ツールの名前の後に括弧に入れて指定します。

jps という名前は大部分の UNIX システムに付属している ps ユーティリティーを真似たものですが、jps を使うと実行中の Java アプリケーションの JVMID を知ることができます。名前が示すとおり、jps は、指定されたマシン上で実行中の Java プロセスのうち、検出可能なすべての Java プロセスの VMID を返します。あるプロセスが jps によって検出されない場合、それはその Java プロセスを追加したり調べたりすることができないということではなく、そのプロセスが検出可能なものとして公開されていない、ということにすぎません。

jps は Java プロセスを検出できると、そのプロセスの起動に使われるコマンドラインを表示します。Java プロセス同士を区別できる、こうした方法は重要です。オペレーティング・システムに関する限り、Java プログラムはすべて「java」だからです。ほとんどの用途で、VMID は注目に値する重要な番号です。

プロファイラーを使い始める

プロファイリング・ユーティリティーを使い始める最も簡単な方法は、demo/jfc/SwingSet2 にある SwingSet2 デモのようなデモ・プログラムを利用する方法です。デモ・プログラムを利用することで、バックグラウンド・プロセスやデーモン・プロセスとして実行されるプロセスによってハングアップするようなことがなくなります。ツールとそのツールによるオーバーヘッドを理解できたら、そのツールを実際のプログラムで試してみます。

デモ・アプリケーションのロードが完了したら、jps を実行し、返された vmid をメモします。最善の結果を得るためには、-Dcom.sun.management.jmxremote プロパティーを指定して Java プログラムを起動します。この指定をしないと、以下に説明するツールで収集されたデータの一部を利用することができません。


2. jstat (sun.tools.jstat)

jstat ユーティリティーを使うと、多種多様な統計を収集することができます。jstat 統計は、コマンドラインで最初のパラメーターとして指定される「オプション」でソートされます。JDK 1.6 では、-options コマンドを付けて jstat を実行すると、利用可能なオプションを一覧表示することができます。オプションの一部をリスト 1 に示します。

リスト 1. jstat のオプション
-class
-compiler
-gc
-gccapacity
-gccause
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcpermcapacity
-gcutil
-printcompilation

このユーティリティーの JDK ドキュメント (「参考文献」を参照) には、リスト 1 の各オプションによって何が返されるかが説明されていますが、これらのオプションの大部分はガーベッジ・コレクション (またはその一部) に関するパフォーマンス情報の収集に使われます。-class オプションを指定すると、ロードされたクラスとアンロードされたクラスが表示され (これにより、アプリケーション・サーバーやコードの中での ClassLoader のリークを検出するための優れたユーティリティーになります)、-compiler オプションと -printcompilation オプションは共に HotSpot JIT コンパイラーに関する情報を表示します。

jstat はデフォルトで、チェックを実行した瞬間の情報を表示します。一定の間隔でスナップショットを取りたい場合には、-options コマンドの後にミリ秒単位で間隔を指定します。すると jstat はモニタリング対象のプロセスの情報のスナップショットを連続的に表示します。特定の回数だけスナップショットを取ってから jstat を終了させたい場合には、間隔や周期の値の後で、その回数を指定します。

例えば、数分前に起動して実行中の SwingSet2 プロセスの VMID が 5756 だったとすると、以下のコマンドによって、ガーベッジ・コレクションのスナップショットのダンプを 250 ミリ秒ごとに 10 回繰り返し生成してから終了するように jstat に指示することができます。

jstat -gc 5756 250 10

さまざまなオプションによる出力や、さらにはオプション自体を警告なしに変更する権利を Sun (現在は Oracle) が保有していることに注意してください。その点が、Sun がサポートしていないユーティリティーを使用する場合の問題点です。jstat 出力の各列に関する完全な詳細については Javadoc を参照してください。


3. jstack (sun.tools.jstack)

Java プロセスの内部、すなわち実行中の複数のスレッドの内部で何が起きているかを知ることは、診断の際によくある難題です。例えばアプリケーションが突然処理を停止した場合、何らかの不足が生じていることは明らかですが、どこでその不足が起きているのか、あるいはなぜ不足が起きているのかは、コードを見るだけでは明らかになりません。

jstack は、アプリケーションの中で実行中のさまざまなスレッドの完全なダンプを返すユーティリティーです。このダンプを利用することで、問題を正確に特定することができます。

対象プロセスの VMID を指定して jstack を実行すると、スタック・ダンプが生成されます。この jstack の動作は、Java プログラムが実行されているコンソール・ウィンドウで Ctrl-Break を押す場合や、VM の中の各 Thread オブジェクトの Thread.getAllStackTraces()Thread.dumpStack() を呼び出す場合と同じです。また jstack を呼び出すと、VM の中で実行中の (必ずしも Thread オブジェクトとして利用できるわけではない) Java 以外のスレッドに関する情報もダンプされます。

-l パラメーターを指定して jstack を実行すると、各 Java スレッドが保持するロックの詳細情報を含む、少し長いダンプが生成され、デッドロックやスケーラビリティーのバグを発見する上で (そしてバグをつぶす上で) 非常に貴重な情報となります。


4. jmap (sun.tools.jmap)

場合によると、扱っている問題がオブジェクトのリークであることがあります (例えば、何千ものオブジェクトを保持している可能性がある ArrayList が解放されるべきときに解放されないなど)。もっと一般的な別の問題として、ガーベッジ・コレクションがアクティブに行われているにもかかわらずヒープが次第に大きくなり、まったく小さくならない、という場合もあります。

オブジェクトのリークを特定しようとする場合、指定された瞬間のヒープの状況をとらえることができ、ヒープが何に使われているのかを調べられると、非常に便利です。jmap はヒープのスナップショットを取ることで、そうした機能の前半部分を提供します。スナップショットが得られると、そのヒープのデータを、次のセクションで説明する jhat ユーティリティーを使って解析することができます。

jmap の使い方は、ここで説明した他のユーティリティーと同じように単純です。スナップショットを取りたい Java プロセスの VMID を jmap のオプションとして指定し、また生成される結果ファイルを表すパラメーターをいくつか指定するだけです。jmap に渡すオプションとしては、ダンプ先のファイル名、そしてテキスト・ファイルを使うかバイナリー・ファイルを使うかの指定があります。最も便利なオプションはバイナリー・ファイルですが、何らかの索引ツールと組み合わせない限りバイナリー・ファイルも役に立ちません (16 進の値で一杯の数メガバイトのテキストを手動で調べるという方法は、1 日を過ごすための方法として最善ではありません)。

もっと簡単に Java ヒープを調べられるように、jmap-histo オプションもサポートしています。-histo を指定すると、ヒープの中で現在頻繁に参照されているオブジェクトのテキスト・ヒストグラムが、そのオブジェクトに使用されている合計バイト数の順にソートされて生成されます。また -histo オプションによって、そのオブジェクトのインスタンスの総数を知ることもでき、それによってインスタンス当たりの相対コストの概算や推測をすることができます。

残念ながら、jmapjstat とは異なり、周期と最大回数を指定できるオプションがありません。ただし、jmap への (または jmap.main() への) 呼び出しをシェル・スクリプトや他のクラスの中でループに入れ、定期的にスナップショットを取ることは比較的容易です。(実際、そうすることは、OpenJDK 自体へのソース・パッチとしても、別のユーティリティーに対する拡張機能としても、jmap に追加するには適切な拡張機能となるはずです。)


5. jhat (com.sun.tools.hat.Main)

ヒープをバイナリー・ファイルにダンプできると、そのヒープ・ダンプのバイナリー・ファイルを jhat を使って解析することができます。jhat によって HTTP/HTML サーバーが作成され、そのサーバーをブラウザーでサーフィンすることで、時間を止めてオブジェクトごとにヒープを調べることができます。オブジェクト参照ごとにヒープをウォークスルーするのは楽しいかもしれませんが、乱雑な状態に対して何らかの自動分析を行った方がより一層役に立つはずです。幸いなことに、jhat はそうした分析を行うための OQL 構文をサポートしています。

例えば、100 文字を越える String すべてに対して OQL クエリーを実行する場合には、以下のようにします。

select s from java.lang.String s where s.count >= 100

これらの結果は、オブジェクトへのリンクとして表示されます。そしてこれらのリンクによって、そのオブジェクトの完全な内容、つまり他のオブジェクトへのフィールド参照が、逆参照可能な追加リンクとして表示されます。また OQL クエリーでは、オブジェクト自体のメソッドを呼び出したり、クエリーの一部として正規表現を使用したり、あるいは組み込みのクエリー・ツールを使用したりすることもできます。クエリー・ツールの 1 つである referrers() 関数を利用すると、指定された型のオブジェクトを参照しているすべての参照元を表示することができます。File オブジェクトを参照しているすべての参照元を見つけるためのクエリーは、以下のようになります。

select referrers(f) from java.io.File f

OQL の完全な構文と OQL の機能については、jhat のブラウザー環境の「OQL Help」ページで説明されています。jhat と OQL の組み合わせは強力であり、動作が正常ではないヒープを、ターゲットを絞って調べることができます。


まとめ

Java プロセスの中で何が起きているかを詳細に調べる必要がある場合、JDK のプロファイリング拡張機能は非常に便利です。この記事で紹介したツールはすべて、単独でコマンドラインから使用することができます。またこれらのツールを JConsole や VisualVM と組み合わせて強力なものにすることもできます。JConsole と VisualVM は Java 仮想マシン全体の概要を調べる上で有効ですが、jstatjmap など、特に焦点を絞ったツールを使うことで、詳細な調査を行うことができます。

次回の「今まで知らなかった 5 つの事項」では、スクリプトを取り上げます。

参考文献

学ぶために

  • 今まで知らなかった 5 つの事項」(Ted Neward 著、developerWorks、2010年) を読み、Java プラットフォームについて皆さんがどれほど知らなかったかを学んでください。この連載は、Java に関する些細な知識をプログラミングのための実用的なヒントに変えることに焦点を絞っています。
  • JDK Tools and Utilities のページを調べ、この記事で説明した実験的なモニタリング・ツールやトラブルシューティング・ツール (jpsjstatjstackjmapjhat) について学んでください。
  • Top 10 Java Performance Troubleshooting Tools」(Sajan Kumar 著、Javalobby、2008年7月) を見てください。JConsole はこのリストの先頭にあり、その後に、この記事の読者が初めて目にするツールがいくつか続いています。
  • Acquiring JVM Runtime Information」(Dustin Marx のブログ、Dustin's Software Development Cogitations and Speculations、2009年6月) では、jpsjinfoJConsole など、JDK に組み込みのモニタリング・ツールや管理ツールを組み合わせるための他の方法を説明しています。
  • Java の理論と実践: パフォーマンスの都市伝説」(Brian Goetz 著、developerWorks、2003年4月) では、Java のパフォーマンスに関する 3 つの有名な「事実」を著者が検証しています。
  • Java の理論と実践: 弱参照でメモリー・リークを塞ぐ」(Brian Goetz 著、developerWorks、2005年11月) を読み、意図に反してオブジェクトが保持されてしまう問題の一般的な原因を検出する方法と解決する方法を学んでください。
  • JVMTI (JVM Tool Interface) のページを調べ、プロファイリング、デバッグ、モニタリング、スレッド分析、カバレッジ分析などのツールをサポートする、Java プラットフォーム・ネイティブのプログラミング・インターフェースについて学んでください。JDI (Java Debug Interface) には、実行状態の VM にリモート・アクセスするためのデバッガーの情報が説明されています。
  • developerWorks の Java technology ゾーンには、Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=506926
ArticleTitle=今まで知らなかった 5 つの事項: Java のパフォーマンス・モニタリング、第 2 回
publish-date=07132010