Java HotSpot VM 以外の多くの VM も、JVM TI を実装しています。バックエンドのリファレンス実装は、ほかのいくつかのプラットフォームに移植されました。さらに、バックエンド以外にも JVM TI のクライアントがあります。もっとも有名なのは、ネイティブコードと Java プログラミング言語コードの両方のデバッグを可能にするアプリケーションエージェント (ネイティブレベルの制御と情報を必要とする) です。バックエンドのクリーンルーム実装を意識する必要はありません。そのようなバックエンドを作成することも可能ですが、相当な労力を必要とします。
一部の VM では、JVM TI の実装に問題があります。そのような VM では、JDWP が直接実装されます。クライアント側では、Java プログラミング言語で作成されていないアプリケーションは、JDI を使用するアプリケーションとして不適格なことがあります。アプリケーションによっては、JDWP のクライアントとして実装することもあります。
JDI は、アプリケーションの静的なビューを提供するようにシステムによって実装されることがあります。また、JDWP のフロントエンドとはまったく違うメカニズムで情報を収集したり VM を制御したりするように実装されることもあります。
各インタフェースを橋渡しするのは、要求とイベントという 2 つのアクティビティーです。要求は、デバッガ側から出されるもので、情報の照会、リモート側の VM やアプリケーションの状態変更の設定、およびデバッグ状態の設定が含まれています。イベントは、debuggee 側から出されるもので、リモート側の VM やアプリケーションの状態変化を示しています。
1 つの実例を調べてみましょう。IDE のスタック表示でユーザーが局所変数をクリックし、その値を要求したとします。IDE は、JDI を使用してその値を取得します。具体的には、getValue
メソッドを呼び出します。次に例を示します。
theStackFrame.getValue(theLocalVariable)ここで、
theStackFrame
は com.sun.jdi.StackFrame
であり、theLocalVariable
は com.sun.jdi.LocalVariable
です。
次に、フロントエンドは、この要求を通信チャネル (たとえば、ソケット) 経由で、debuggee プロセスが動作しているバックエンドに送ります。そのとき、フロントエンドは、その要求を JDWP に準拠したバイトストリームの形式に変換します。具体的に言うと、フロントエンドは GetValues コマンド (バイト値 1) をStackFrame コマンドセット (バイト値 16) で送り、そのあとにスレッド ID、フレーム ID などが続きます。
バックエンドは、そのバイトストリームを解析し、JVM TI を介して VM に照会を送ります。具体的には、要求された値が整数だとすると、次のような JVM TI 関数の呼び出しを実行します。
error = jvmti->GetLocalInt(frame, slot, &intValue);バックエンドは、ソケット経由で応答パケットを返送します。そのパケットには
intValue
の値が入っており、JDWP に準拠したデータ形式になっています。フロントエンドは、応答パケットを解析し、その値を getValue
メソッド呼び出しの値として返します。最後に、IDE は、返された値を表示します。
デバッグ状態を変更する要求も、同様の方法で処理されます。たとえば、ブレークポイントを設定するという要求は、同様のステップで処理されます。もちろん、呼び出される JDI メソッドや、送信される JDWP コマンドや、呼び出される JVM TI 関数は違います。さらに、フロントエンドとバックエンドは、単にデータをやり取りする以上のことを行います。アクティビティーを追跡およびスケジューリングし、情報を変換、フィルタリング、およびキャッシュします。したがって、ブレークポイントを設定する要求は、値を取得する照会とはかなり違った仕方で処理されますが、通信の手順は同じです。
デバッグしているアプリケーションがこのブレークポイントに達すると、何が起こるのでしょうか。今度は、イベントの出番になります。仮想マシンは、JVM TI インタフェースを介してイベントを送ります。具体的には、仮想マシンは、イベント処理関数を呼び出して、ブレークポイントを渡します。
バックエンドは、イベント処理関数を次のように設定しています。
static void Breakpoint(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread, jmethodID method, jlocation location) { ...このバックエンド関数は、関心のあるイベントをフィルタリングし、そのイベントをキューに入れ、ブレークポイントイベント用に定義された JDWP 形式のソケットを介してそのイベントを送信するという、一連のアクティビティーを開始します。フロントエンドは、そのイベントをデコードして処理し、最終的には JDI イベントを生成します。具体的には、JDI イベントは、
com.sun.tools.jdi.event.BreakpointEvent
として公開されます。その後、IDE は、そのイベントをイベントキューから取り出して取得します。
theEventQueue.remove()ここで、
theEventQueue
は com.sun.jdi.event.EventQueue
です。IDE は、JDI を介して多くの照会呼び出しを実行することにより、表示を更新すると予想されます。
バックエンドのリファレンス実装を新しいプラットフォームに移すには、多くの場合、ソースにわずかの変更 (数行のみ) を加えるか、ソースをまったく変更せずに、再コンパイルするだけで済みます。同じプラットフォーム上で新しい VM を使用する場合は、バックエンドのバイナリコードは多くの場合そのまま動作します。ただし、それは Java プログラミング言語のコードではないため、内容を理解することはできません。このドキュメントではライセンスの問題には触れていません。
フロントエンドの実装は Java プログラミング言語で作成されているため、どのプラットフォームまたは VM でも動作します。ただし、一部のシステムでは、コネクタコードの機能の一部を拡張する必要がある場合もあります。たとえば、フロントエンドのリファレンス実装に含まれている起動ツールでは、仮想マシンが Java SE の規則を使って起動されることが前提です。JDI のユーザーが自分たちの希望に合わせて起動ツールの構文を決めることもできますが、一般に、デバッガアプリケーションでは、その構文が JDI 実装の側で決められていると想定します。別の種類の通信チャネル (たとえば、シリアル接続) が必要な場合は、JDK 5.0 で導入されたサービスプロバイダインタフェースを使用して、その機能も追加する必要があります。