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で導入されたサービス・プロバイダ・インタフェースを使用して、その機能も追加する必要があります。