最近、Habréでバイトコード操作に影響する記事が登場しました。 その構造に関する次の記事を公開した理由
Javaプラットフォームには2つの機能があります。 クロスプラットフォームを保証するために、プログラムは最初に低レベルの中間言語であるバイトコードにコンパイルされます。 2番目の機能は、拡張可能なクラスローダーを使用して実行可能クラスをロードすることです。 このメカニズムにより柔軟性が向上し、起動時に実行可能コードを変更したり、プログラム実行中に新しいクラスを作成およびロードしたりできます。
この手法は、AOPの実装、テストフレームワーク、ORMの作成に広く使用されています。 特に、jvmをクラスタリングし、バイトコードの変更を最大限に活用するという美しいアイデアを持つ製品であるterracottaに言及したいと思います。 この投稿では、この強力なバンドルの最初の部分であるバイトコードの構造のレビューに専念します。
javaの各クラスは、1つのコンパイル済みファイルに対応しています。 これは、サブクラスまたは匿名クラスにも当てはまります。 このようなファイルには、クラスの名前、その親、実装するインターフェースのリスト、フィールドとメソッドのリストに関する情報が含まれています。 インポートディレクティブを含む情報をコンパイルした後、その情報は失われ、すべてのクラスの名前がフルパスで指定されることに注意することが重要です。 たとえば、java / lang / StringはStringに書き込まれます。
最も興味深いのは、バイトコードのクラスメソッドがどのように見えるかです。 次のクラスが何に変換するかを観察します。
パッケージ組織。
クラスTest {
プライベート文字列名。
public String getName(){
名前を返す;
}
public void setName(文字列名){
this.name = name;
}
}
見出しから始めましょう。 メソッドの名前、メソッドがパラメーターなしで呼び出されること、および返される引数の型に関する情報が含まれます。
バイトコードはスタック指向の言語であり、構造はアセンブラーに似ています。 データを使用して操作を実行するには、最初にそれらをスタックに配置する必要があります。 オブジェクトからフィールドを取得します。 これを行うには、スタックに配置する必要があります。 バイトコードには変数名がなく、番号があります。 現在のオブジェクトまたはthis変数の参照番号はゼロです。 次に、実行可能メソッドのパラメーターがあります。 次に、残りの変数。
ALOAD 0コマンドは、この変数をスタックにプッシュします。 リンク以外のデータ型をスタックに配置するには、別のコマンドを使用する必要があります。 長い間はLLOADになり、ダブル[]ではDALOADになります。
次のGETFIELDコマンドは、スタックからオブジェクトへのリンクを削除し、プリミティブ型またはこのオブジェクトのフィールドへのリンクを配置します。 彼女には2つの選択肢があります。 最初の名前はクラスで、2番目の名前は変数です。 変数が静的である場合、最初に何もスタックに置く必要はなく、コマンドは同じパラメーターでGETSTATICに置き換える必要があります。
最後のコマンドは、メソッドが完了したことを示し、リンクタイプの値をスタックから返します。
セッターの構造はもう少し複雑です。
public setName(Ljava / lang / String;)V
負荷0
負荷1
PUTFIELD組織/テスト名
戻る
このメソッドは何も返しません。 最初の2つのコマンドは、this変数と実行可能メソッドのパラメーターをスタックにプッシュします。 次に、PUTFIELDコマンド(静的フィールドの場合はPUTSTATIC)が呼び出されます。これは、オブジェクトフィールドの値を設定し、スタックから最後の2つの値を削除します。 最後のコマンドは、メソッドを終了することです。
オブジェクトにメソッドをいくつか追加し、それらに対応するバイトコードを確認します。
public void forTest(ブールb){
System.out.prinln(b);
}
public Long testMethods(コレクション<Long> testInterface){
Long a = System.curretM();
forTest(testInterface.contains(a));
を返します;
}
testMethodには次のビューがあります。
INVOKESTATIC java / lang / System currentTimeMillis()J
LSTORE 2
負荷0
負荷1
LLOAD 2
INVOKESTATIC java / lang / Long valueOf(J)Ljava / lang / Long;
INVOKEINTERFACE java / util / Collection contains(Ljava / lang / Object;)Z
INVOKESTATIC java / lang / Boolean valueOf(Z)Ljava / lang /ブール値;
INVOKEVIRTUAL org / Test forTest(Ljava / lang /ブール;)V
LLOAD 2
INVOKESTATIC java / lang / Long valueOf(J)Ljava / lang / Long;
戻る
最初のコマンドは、Systemクラスの静的メソッドを呼び出します。 2番目は、2番目の番号を持つ変数でcurrentTimeMillisメソッドを呼び出した結果を記憶しています。 次に、変数this、メソッドパラメーター、および変数番号2をスタックに配置します。 変数をjava / lang / Long型に変換します。 そして、実行可能パラメータのメソッドを呼び出して、コレクションに含まれていることを確認します。 インターフェイスパラメータがあるため、INVOKEINTERFACEコマンドが使用されます。 クラスメソッドの場合は、INVOKEVIRTUALを使用する必要があります。 オブジェクトまたはインターフェイスでメソッドを呼び出すには、オブジェクトがスタック上にあり、次に呼び出されたメソッドのパラメーターが必要です。 メソッド呼び出しの結果として、メソッドが何も返さない場合、結果で置き換えられるか、単にスタックから削除されます。 最後の3つのコマンドは、変更をスタックに入れ、オブジェクトに変換し、メソッドの値として返します。
バイトコードへのエクスカーションを完了するには、最後のメソッドを追加して、ループと条件ステートメントを見てください。
public void testAriphmentics(){
int i = -17;
while(i <10){
if(i <0){
i = i + 7;
}
i = i * 13;
}
}
バイトコードでは次のようになります
ACC_FINAL -17
ISTORE 1
レーベル:L1466604866
ILOAD 1
ACC_FINAL 10
IF_ICMPGE L329949514
ILOAD 1
IFGE L658705244
ILOAD 1
ACC_FINAL 7
Iadd
ISTORE 1
レーベル:L658705244
ILOAD 1
ACC_FINAL 13
IMUL
ISTORE 1
GOTO L1466604866
レーベル:L329949514
戻る
最初の2つのコマンドは、変数i(番号1)を-17に初期化します。
次に、サイクルの本体を開始します。 どのラベルが必要かを実装するには、
ジャンプ命令と条件文。 メソッドの本体には、最大3つのラベルがあります。 最初のラベルは、サイクルの開始を示します。 2番目は条件ステートメントに必要で、後者はループの終わりを示します。 catchステートメントには、ジャンプラベルパラメーターが1つあります。 呼び出す前に、比較された値がスタック上になければなりません。 ゼロと比較するために、別のコマンドが使用されます。 各タイプには独自の比較演算子があります。 intの場合、IF_ICMPGEです。 比較後、比較された値はスタックから削除されます。 2つの変数を使用する算術演算の場合、条件演算子と同様に、それらを最初にスタックに配置する必要があります。 実行後、それらはスタックから削除され、結果がその場所に配置されます。
バイトコードへのこの短いエクスカーションは終了しました。例外や同期などの問題は発生しませんでした。 読者がバイトコードを理解していれば、簡単に対処できることを願っています。 次のパートでは、バイトコードの変更に使用されるツールを見ていきます。
http://math-and-prog.blogspot.com/2009/08/java.html