7

私はバイトコードインストルメンテーションを書いています。今、私はオブジェクトの存在下でそれを行う方法を見つけようとしています。JVMS(セクション4.9.4)で読んだ2行についていくつか説明したいのですが。

1)「ベリファイアは、初期化される前に新しいオブジェクトを使用するコードを拒否します。」

私の質問は、ここで「使用」とはどういう意味ですか?私はそれが意味していると推測しています:それをメソッド属性として渡すか、それを呼び出しGETFIELDPUTFIELD、またはそれに対して任意のインスタンスメソッドを呼び出します。他の禁止されている使用法はありますか?そして、、、などDUPの他の指示に従うことは許可されていると思います。LOADSTORE

2)「そのメソッドがmyClassの別のインスタンス初期化メソッドまたはこれに対する直接のスーパークラスを呼び出す前に、メソッドがこれに対して実行できる唯一の操作は、myClass内で宣言されたフィールドを割り当てることです。」

つまり、<init>メソッドでは、GETFIELDとPUTFIELDは、別のメソッド<init>が呼び出される前に許可されます。ただし、Javaでは、を呼び出す前にインスタンスフィールドで操作を実行すると、コンパイルエラーが発生しますsuper()this()誰かがこれを明確にすることができますか?

3)もう1つ質問があります。オブジェクト参照はいつ初期化され、自由に使用できるようになりますか?JVMSを読んだところ、オブジェクトが初期化されるかどうかは、各メソッド次第であるという答えを思いつきました。ある時点で、オブジェクトはメソッドに対して初期化できますが、他のメソッドに対しては初期化できません。具体的には、オブジェクトは、<init>そのメソッドが返すときに呼び出されると、そのメソッドに対して初期化されます。

たとえば、main()メソッドがオブジェクトを作成して呼び出し、そのオブジェクト<init>がスーパークラスのを呼び出したとします<init>。から戻った後super()、オブジェクトはによって初期化されたと見なされますが<init>、まだ初期化されていませんmain()<init>これは、の後super()に、main()に戻る前であっても、オブジェクトをパラメーターとしてメソッドに渡すことができることを意味しますか?

誰かがこの分析全体が真実であることを確認できますか?お時間をいただきありがとうございます。

ps:私は実際に同じ質問をSunフォーラムに投稿しましたが、回答はあります。ここでもっと運が良ければいいのにと思います。ありがとうございました。

アップデート

まずはご回答とお時間をいただきありがとうございます。明確な答えは得られませんでしたが(多くの質問があり、そのうちのいくつかは少し曖昧でした)、あなたの答えと例、およびその後の実験は、JVMがどのように機能するかをより深く理解するのに非常に役立ちました。

私が発見した主なことは、Verifierの動作が実装やバージョンによって異なることです(これにより、バイトコード操作の作業がはるかに複雑になります)。問題は、JVMに準拠していないか、検証者の開発者からのドキュメントが不足しているか、またはJVMが検証者の領域に微妙なあいまいさを持っていることにあります。

最後にもう1つ、SO Rocks !!! 同じ質問を公式のSunJVM仕様フォーラムに投稿しましたが、今まで答えがありませんでした。

4

3 に答える 3

4

「ベリファイアは、初期化される前に新しいオブジェクトを使用するコードを拒否します。」

バイトコード検証では、ベリファイアはリンク時に機能するため、メソッドのローカル変数のタイプが推測されます。メソッド引数のタイプは、クラスファイルのメソッドシグネチャにあるため、既知です。他のローカル変数のタイプは不明であり、推測されるため、上記のステートメントの「用途」はこれに関連していると思います。

編集:JVMのセクション4.9.4には次のよう書かれています。

クラスmyClassのインスタンス初期化メソッド(§3.9)は、新しい未初期化オブジェクトをローカル変数0のthis引数と見なします。そのメソッドがmyClassの別のインスタンス初期化メソッドまたはこれに対する直接スーパークラスを呼び出す前に、メソッドが実行できる唯一の操作これは、myClass内で宣言されたフィールドを割り当てています。

上記のステートメントでのフィールドのこの割り当ては、オブジェクトのメモリが割り当てられたときの、インスタンス変数のデフォルトの初期値(intが0、floatが0.0fなど)への「初期」初期化です。仮想マシンがオブジェクトのインスタンス初期化メソッド(コンストラクター)を呼び出すとき、インスタンス変数のもう1つの「適切な」初期化があります。John Horstmannによって提供され


リンクは、物事を明確にするのに役立ちました。したがって、これらのステートメントは当てはまりません。<init>「これは、メソッド内getfieldでそれを意味するものではなく、putfield別のメソッドが呼び出される前に許可さ<init>れます。」getfieldと_putfield命令は、クラス(またはクラスのインスタンス)のインスタンス変数(フィールド)にアクセス(および変更)するために使用されます。そして、これはインスタンス変数(フィールド)が初期化されたときにのみ発生する可能性があります。」

JVMSから:

各インスタンス初期化メソッド(§3.9)は、クラスObjectのコンストラクターから派生したインスタンス初期化メソッドを除き、インスタンスメンバーにアクセスする前に、この別のインスタンス初期化メソッドまたは直接スーパークラススーパーのインスタンス初期化メソッドを呼び出す必要があります。ただし、現在のクラスで宣言されているこのインスタンスフィールドは、インスタンス初期化メソッドを呼び出す前に割り当てることができます。

Java仮想マシンは、暗黙的または明示的にクラスの新しいインスタンスを作成するときに、最初にヒープにメモリを割り当てて、オブジェクトのインスタンス変数を保持します。メモリは、オブジェクトのクラスとそのすべてのスーパークラスで宣言されたすべての変数に割り当てられます。これには、非表示のインスタンス変数も含まれます。仮想マシンが新しいオブジェクト用のヒープメモリを確保するとすぐに、インスタンス変数をデフォルトの初期値に初期化します。仮想マシンが新しいオブジェクトにメモリを割り当て、インスタンス変数をデフォルト値に初期化すると、インスタンス変数に適切な初期値を与える準備が整います。Java仮想マシンは、clone()呼び出しのためにオブジェクトが作成されているかどうかに応じて、これを行うために2つの手法を使用します。clone()が原因でオブジェクトが作成されている場合、仮想マシンは、クローンが作成されているオブジェクトのインスタンス変数の値を新しいオブジェクトにコピーします。それ以外の場合、仮想マシンはオブジェクトのインスタンス初期化メソッドを呼び出します。インスタンス初期化メソッドは、オブジェクトのインスタンス変数を適切な初期値に初期化します。getfieldそして、この後のみ、とを使用できますputfield

Javaコンパイラは、コンパイルするクラスごとに少なくとも1つのインスタンス初期化メソッド(コンストラクタ)を生成します。クラスがコンストラクターを明示的に宣言していない場合、コンパイラーは、スーパークラスの引数なしコンストラクターを呼び出すだけのデフォルトの引数なしコンストラクターを生成しました。そして当然のことながら、呼び出しの前にインスタンスフィールドで操作を行うと、コンパイルエラーが発生します super()。 メソッドには、別のメソッドの呼び出し、インスタンス変数初期化子を実装するコード、コンストラクター本体のコードの3種類のコードを含めることができます。コンストラクターが同じクラス内の別のコンストラクターの明示的な呼び出し(呼び出し)で始まる場合、それに対応するthis()

<init><init>this()<init>メソッドは2つの部分で構成されます。

  • 同じクラスの <init>メソッドの呼び出し
  • 対応するコンストラクターの本体を実装するバイトコード

コンストラクターが呼び出しで始まらずthis()、クラスがObjectでない場合、<init>メソッドには次の3つのコンポーネントがあります。

  • <init>スーパークラスメソッドの呼び出し
  • インスタンス変数初期化子のバイトコード
  • 対応するコンストラクターの本体を実装するバイトコード


コンストラクターが呼び出しで開始せずthis()、クラスがObjectである(そしてObjectにスーパークラスがない)場合、その<init>メソッドはスーパークラス<init>メソッドの呼び出しで開始できません。コンストラクターがスーパークラスコンストラクターの明示的な呼び出し(呼び出し)で始まる場合super()、その<init>メソッドは対応するスーパークラス<init>メソッドを呼び出します。



これはあなたの最初と2番目の質問に答えると思います。

更新しました:

例えば、

  class Demo
  {
     int somint;

     Demo() //first constructor
     {
      this(5);
      //some other stuff..
     }

     Demo(int i) //second constructor
     {
      this.somint = i;
      //some other stuff......
     }
     Demo(int i, int j) //third constructor
     {
      super();
      //other stuffff......
     }
  }

コンパイラ(javac)からの上記の3つのコンストラクタのバイトコードは次のとおりです。

Demo();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   iconst_5
   2:   invokespecial   #1; //Method "<init>":(I)V
   5:   return

Demo(int);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   iload_1
   6:   putfield        #3; //Field somint:I
   9:   return

Demo(int, int);
  Code:
   Stack=1, Locals=3, Args_size=3
   0:   aload_0
   1:   invokespecial   #2; //Method java/lang/Object."<init>":()V
   4:   return

最初のコンストラクターでは、<init>メソッドは同じクラスのメソッドを呼び出すことから始まり<init>、対応するコンストラクターの本体を実行します。コンストラクターはで始まるため、this()対応する<init>メソッドには、インスタンス変数を初期化するためのバイトコードが含まれていません。

2番目のコンストラクターでは<init>、コンストラクターのメソッドは次のようになります。

  • スーパークラス<init>メソッド、つまりスーパークラスコンストラクター(argメソッドなし)の呼び出しではsuper()、最初のステートメントとして明示的なものが見つからなかったため、コンパイラーはデフォルトでこれを生成しました。
  • インスタンス変数を初期化するためのバイトコードsomeint
  • コンストラクター本体の残りの部分のバイトコード。
于 2010-07-19T07:10:40.610 に答える
4

Java言語で指定されているものとは異なり、バイトコードレベルで、スーパークラスコンストラクターを呼び出す前に、コンストラクター内のクラスのフィールドにアクセスできます。次のコードは、asmライブラリを使用してそのようなクラスを作成します。

package asmconstructortest;

import java.io.FileOutputStream;
import org.objectweb.asm.*;
import org.objectweb.asm.util.CheckClassAdapter;
import static org.objectweb.asm.Opcodes.*;

public class Main {

    public static void main(String[] args) throws Exception {
        //ASMifierClassVisitor.main(new String[]{"/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test.class"});
        ClassWriter cw = new ClassWriter(0);
        CheckClassAdapter ca = new CheckClassAdapter(cw);

        ca.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "asmconstructortest/Test2", null, "java/lang/Object", null);

        {
            FieldVisitor fv = ca.visitField(ACC_PUBLIC, "property", "I", null, null);
            fv.visitEnd();
        }

        {
            MethodVisitor mv = ca.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitInsn(ICONST_1);
            mv.visitFieldInsn(PUTFIELD, "asmconstructortest/Test2", "property", "I");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 1);
            mv.visitEnd();
        }

        ca.visitEnd();

        FileOutputStream out = new FileOutputStream("/Temp/Source/asmconstructortest/build/classes/asmconstructortest/Test2.class");
        out.write(cw.toByteArray());
        out.close();
    }
}

このクラスのインスタンス化は、検証エラーなしで正常に機能します。

package asmconstructortest;

public class Main2 {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        System.out.println(test2.property);
    }
}
于 2010-07-19T11:36:02.327 に答える
1

OpenJDKソースのコピーをダウンロードして、ベリファイアが実際にチェックしているものを確認することをお勧めします。他に何もないとしても、それはJMV仕様が何を言っているかを理解するのに役立つかもしれません。

(ただし、@ Joachimは正しいです。仕様に記載されていることではなく、ベリファイアの実装が行うことに依存することはかなり危険です。)

于 2010-07-19T07:06:31.127 に答える