これまでは、用途に応じて「hprof」(JDK付属のプロファイラ)や「jstat」(JavaVM統計データ監視ツール)などのJDK付属ツール、「tcpdump」「strace」などのOS付属ツールを組み合わせて情報を取得するケースが大半でした。しかし、SystemTapではスクリプトを記述するだけで、数多くの項目を簡単に、一元的に取得・加工が可能です。
これまでは「JVMTI(Java Virtual Machine Tool Interface)」のエージェントを作らなければ検知できなかったJavaVM(HotSpot VM)の内部イベントをフックし、VM内部の動作状況を把握できます。
例えば、特定メソッドの実行時間やGC処理時間、VM内部関数の動作状況を確認できます。
「libc」の提供するAPIやカーネル内の状態なども一元的に把握できます。
例えば、特定のエンドポイントにソケットを接続したスレッドやローカルポート番号をリストアップするといったことが可能です。
JNI(Java Native Interface、JavaのプログラムからC/C++言語などで開発されたネイティブコードのプログラムを利用するためのAPI)関数の呼び出しと戻りにフックを掛け、それぞれの関数に渡される引数や戻り値を、デバッガを介さずに取得できます。
例えば、JNIライブラリ内で原因不明のエラーが発生しているときに、デバッガのアタッチやソース変更をしないで障害発生時の関数呼び出し状況を詳細に把握できます。
一部の機能を除き、javaプロセス起動前に仕込んでおく準備などは必要ありません。
スクリプトを用意し、情報を取得したいタイミングでコマンドを1回叩くだけで、すぐに必要な情報を取得可能です。
SystemTapのスクリプトには「guru」と呼ばれるモードがあり、C言語のソースコードを埋め込むことが許されています。
スクリプトは最終的にC言語で記述されたカーネルモジュール向けソースコードに変換・コンパイルされ、カーネルに組み込まれます。そのため、「kallsyms」コマンド(カーネルの全シンボル情報)などを利用してカーネル内の関数や変数のアドレスさえ分かってしまえば、そこを直接操作することが実現できます。
筆者は特定状況下でkillシステムコールのカーネル内実装(sys_kill関数)を直接呼び出すことで、スレッドダンプやコアイメージを取得しています。
また、定義済みのポイントだけでなく、HotSpot VMやJavaクラスライブラリのJNI実装部の任意のポイントにプローブを掛けることも可能なので、OpenJDKのソースコードレベルでの理解やデバッグにも役立てられます。
SystemTapでは、主に「プローブポイント」と呼ばれる、各種イベントが呼び出される個所を使用します。プローブポイントは、(ネイティブレベルの)関数の入りと戻りや命令アドレスなどさまざまなものに仕掛けられます。
FedoraなどのLinuxディストリビューションで配布されるOpenJDKは、ビルド時にIcedTeaの「configure」コマンドで「--enable-systemtap」オプションが付加されており、HotSpot VMに組み込まれている「静的プローブポイント」というものが有効化されています。これにより、VM内のさまざまな個所でプローブポイントが引数とともに使用可能になり、VM内部の挙動を簡単に把握できます。
HotSpot VMで使用可能なプローブの種類(プロバイダ)は、大きく3つに分けられます。hs_private以外のプローブポイントは「probe {プローブポイント名}」だけの、簡単な記述でスクリプトの記述が可能です。これらのプローブポイントの中には、あらかじめ定義された変数の参照が可能なものがあります。
プローブポイントの数は非常に多いので、よく使いそうなプローブポイントの説明は、後編でスクリプトの実装と絡めて行います。この他、Tapsetの中でプローブポイントや使用可能な変数について詳細な説明がされているので、ぜひお手元のOpenJDKで確認してみてください。
「$JAVA_HOME/tapset/hotspot.stp」で定義されている、HotSpot VMで発生する各種イベントに対するプローブです。
JVMTIイベントと同じタイミングで発生するもの、SystemTapでしか取得できないイベントなどが存在します。プローブポイントは「hotspot.{プローブ名}」で定義されています。
「$JAVA_HOME/tapset/hotspot_jni.stp」で定義されている、JNI関数の入りと戻りをトリガーに持つプローブです。引数や戻り値の確認が可能です。
関数呼び出し時のプローブは「hotspot.jni.{JNI関数名}」、関数から戻るときのプローブ(returnプローブ)は 「hotspot.jni.{JNI関数名}.return」で定義されています(使い方は、次ページで解説します)。
JNI関数のプローブポイントのうち、CallNonvirtual*関数向けのものはIcedTeaより提供されるTapsetと、JavaVM内に埋め込まれた静的プローブポイントの定義が一致せず、正確なデータが取得できないことが判明しました。
NTT OSSセンタはIcedTeaの開発メーリングリストに問題を提起し、および修正パッチを提供し、採用されました。現在、OpenJDK 6向けIcedTea 6の次期リリースブランチには上記パッチがマージされています(参考「icedtea6: ddd219edd416」)。
定義されているTapsetが存在しない、隠しプローブです。VMそのもののデバッグなどに有用です。
使用する場合はprocess句を使用して、冗長なプローブポイント定義をしなければいけません。
「OpenJDK+SystemTapでできる6つの鬼凄」の【5】で、「一部の機能を除き、javaプロセス起動前に仕込んでおく準備などは必要ない」と記載しましたが、事前に仕込みをしておかないと機能しないプローブポイントも存在します。
大きくの3つに分類できますが、これらを有効にするためには、以下の表のようにjavaの起動オプションを追加しなければいけません。
名前から想像できるように、これらのプローブポイントはJavaVMが動作している間は大量に呼び出されることになります。そのため、これらをうかつに有効化しておくと思わぬ性能低下を引き起こすことがありますので、ご注意ください(※デフォルトでは、どれも無効化されていますが、これは性能問題を未然に防ぐためです)。
以下に挙げる3つ(メソッドの出入り、オブジェクト関連、モニタ関連)をすべて有効化するには、java起動オプションに「-XX:+ExtendedDTraceProbes」を追加します。
起動オプション | 対応するプローブポイント | |
---|---|---|
-XX:+ExtendedDTraceProbes | 以下の全プローブポイントを有効にする | |
-XX:+DTraceMethodProbes 【1】Javaメソッド関連プローブの有効化 |
hotspot.method_entry | メソッドの呼び出し |
hotspot.method_return | メソッドからの戻り | |
-XX:+DTraceAllocProbes 【2】オブジェクト関連プローブの有効化 |
hotspot.object_alloc | 新規オブジェクトのアロケート |
-XX:+DTraceMonitorProbes 【3】モニタ関連プローブの有効化 |
hotspot.monitor_wait | Object.wait()で待機状態に入った |
hotspot.monitor_waited | Object.wait()による待機を終了した | |
hotspot.monitor_notify | Object.notify()が呼び出された | |
hotspot.monitor_notifyAll | Object.notifyAll()が呼び出された | |
hotspot .monitor_contended_enter |
他スレッドに保持されているモニタに入った(競合状態) | |
hotspot .monitor_contended_entered |
競合状態のモニタを獲得した | |
hotspot.monitor_contended_exit | 競合するモニタから抜けた | |
次ページでは、実際にOpenJDK+SystemTapを動かしてみます。
Copyright © ITmedia, Inc. All Rights Reserved.