初期化プロセスのJavaのクラスローディング(境界の質問)

クラスまたはインタフェースの初期化プロセスは、初期化メソッドを実行します<clinit>この方法は、同じ順序で、ソースコード内の2つの部分コード命令にクラス静的フィールドおよび文の静的割り当て命令ブロック(静的{})を含むクラス・ファイルにコンパイル時にコンパイラによって生成されます。

次の状況では、(Cで示す)タイプ初期化をトリガします。

  • 新しい(オブジェクトを作成します)GetStatic(クラスフィールドを取得)(クラスフィールドの割り当てに)putstatic、またはinvokestatic(クラスメソッドを呼び出す)命令が実行され、Cのインスタンスを作成し、/ Cセットstaticフィールド、静的メソッドのCの呼び出しを取得します。

    あなたはクラスのフィールドが一定値プロパティと一定で取得した場合、初期化をトリガしません

  • 最初の呼び出しは、java.lang.invoke.MethodHandleのインスタンスを返しREF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecialメソッドハンドルタイプ。

  • なクラスとしての反射呼び出し、パッケージ内java.lang.reflect`クラス

  • Cは、サブクラス化するクラスである場合には<clinit>、メソッド呼び出しの前に、最初の呼び出しのCの<clinit>方法

  • Cは、インターフェース、および非定義されている場合abstract、非static方法、(直接的または間接的に)の実装では、初期化メソッドを実行する<clinit>最初のCを初期化します

  • メインクラスとしてC(mainメソッドを含みます)

それは静的であってもクラス初期故障を遮断原因となるいくつかの時間のかかる操作を実行見ることができます} {

クラスの初期化の前に、操作する最初のリンク

初期化効率をスピードアップするために、JVMはマルチスレッド実行の初期化動作で、一度に複数のスレッドが存在し得るクラスを初期化しようとすると、クラス初期化プロセスであってもよい再帰をトリガした、唯一のスレッドが初期化することを確実にするように、JVM必要がクラスを初期化します初期化プロセスを確保するための行動、状態やミューテックスを維持することにより、JVMがすでに証明されたクラスはスレッドセーフです。

クラスの仮想マシンの状態:

  • クラスは、検証と準備ができて、ではなく、初期化されています
  • クラスはスレッドによって初期化されます
  • クラスの初期化が完了している、あなたが使用することができます
  • クラスの初期化に失敗

実際には、仮想マシンのクラス定義の状態を参照し、そのようなホットスポットとして上記のつ以上の種類であってもよく、上記の

状態に加えて、クラスを初期化する前に、最初のオブジェクトのこのクラス(モニタ)に関連付けられているロックを取得する必要があり、LCと称します。

(jvm1.8仕様)は次のようにCクラスまたはインタフェースの初期化プロセスです。

  1. C.ロックを待っているのLC取得

  2. Cが別のスレッドによって初期化されている場合は、LC、Cおよびブロックのリリースが初期化されるまで、現在のスレッドは完了です。

    スレッドは、初期化プロセスに影響を中断しません

  3. 現在のスレッドがCを初期化されている場合、Cは確かに初期化時間と再帰。LCリリースで初期化をトリガし、通常の状態に戻ります。

  4. 状態Cが初期化されている場合、LCの解放とは、通常の状態に戻ります。

  5. 状態は、Cのための初期化LCを解放し、スローに失敗した場合はNoClassDefFoundError、例外を。

  6. それ以外の場合は、現在のクラスCの状態が初期化されて記録し、スレッドを初期化するために、現在のスレッドを設定し、LCを解放します。

    その後、各Cバイトコードファイルを初期化するために、ConstantValue属性final staticフィールドを。

    **注:** JVM仕様は、初期化フェーズでの割り当ての定数を定義した<clinit>特定の実装の実装が厳密に接着されないことが前に。ホットスポットを作成した仮想マシンのバイトコードの解析処理は_java_mirror、各時定数画像フィールドタイプに割り当てられています。

  7. Cクラスであり、その親クラスが開始されていない場合、次に、SCは、その親クラスと呼ばれるSI1, ..., SInインタフェース(直接的または間接的に)の少なくとも1つの非抽象的、非静的メソッドを含むCで実装と称される。SCを初期化し、順番にすべての注文の親インターフェイスは、再帰の代わりに(のインターフェースCのリストに従って、私は直接Cで実装されたインタフェースの継承階層の順序を決定するinterfacesIの初期化、初期化I〜第ループの前に、オーダー)親インターフェース(リストに従って、Iインタフェースinterfaces順)。

  8. 次に、カスタムクラスローダが(デバッグ用)アサーションになっている表示します。

    // ClassLoader
    
    // 查询类是否开启了断言
    // 通过#setClassAssertionStatus(String, boolean)/#setPackageAssertionStatus(String, boolean)/#setDefaultAssertionStatus(boolean)设置断言
    boolean desiredAssertionStatus(String className);
    复制代码
  9. 次に、初期化方法Cを実行します<clinit>

  10. 初期化Cが正常に完了した場合、初期化として、LCおよびCの状態へのアクセスがマークされている、すべての待機中のスレッドをウェイクアップし、ロックLCを解除し、初期化プロセスは完了です。

  11. そうでない場合、初期化方法はE.場合Eはない例外をスローする必要がありError、またはサブクラスを作成するExceptionInInitializerError(パラメータとしてEで)インスタンスをメモリオーバーフローために作成されていない場合、この例の次のステップは、Eを置き換えるためにExceptionInInitializerError、例えばOutOfMemoryError交換E.

  12. 取得LC標識Cの初期化状態を、すべての待機中のスレッド、解除通知、エラーであるLC(前のステップを参照)Eまたは他の代替によって、異常戻ります。

初期化することを提供、(ロックを4/5にし、放出する)ロックを取得するステップ1でキャンセル、完了した場合、仮想マシンは、Javaメモリモデルによれば、それが決定することができ、このプロセスを最適化することができ、すべてが事前発生関係をロックおよびロックを最適化するときに存在します。

次に、例を見て:

interface IA {
	Object o = new Object();
}

abstract class Base {

	static {
		System.out.println("Base <clinit> invoked");
	}
	
	public Base() {
		System.out.println("Base <init> invoked");
	}

	{
		System.out.println("Base normal block invoked");
	}
}

class Sub extends Base implements IA {
	static {
		System.out.println("Sub <clinit> invoked");
	}

	{
		System.out.println("Sub normal block invoked");
	}

	public Sub() {
		System.out.println("Sub <init> invoked");
	}
}

public class TestInitialization {

	public static void main(String[] args) {
		new Sub();
	}
}

复制代码

ホットスポット上で実行中の仮想マシン:

javac TestInitialization.java && java TestInitialization
复制代码

:初期化シーケンスがある見ることができる父类静态构造器 -> 子类静态构造块 -> 父类普通构造块 -> 父类构造器 -> 子类普通构造快 -> 子类构造器順序に関係なく、および一般的な構成の高速前に、インスタンスコンストラクタの呼び出し。

インターフェースは{}静的に追加することができないので、逆コンパイルルックにより生成することができる<clinit>方法

無インスタンスコンストラクタクラス定義した場合、コンパイラは、親クラス内のデフォルトのコンストラクタを呼び出し、引数なしのデフォルトコンストラクタを生成します

クラスは、代入文のコードブロック内の静的変数や静的変数ではない場合、生成する必要はありません<clinit>

最後に、顔いくつかの関連質問:

  1. ここではどのようなコードを出力するのか?

    public class InitializationQuestion1 {
    
        private static InitializationQuestion1 q = new InitializationQuestion1();
        private static int a;
        private static int b = 0;
    
        public InitializationQuestion1() {
            a++;
            b++;
        }
    
        public static void main(String[] args) {
            System.out.println(InitializationQuestion1.a);
            System.out.println(InitializationQuestion1.b);
        }
    }
    复制代码

    B&Qそれの背面に声明?出力は何ですか?

  2. ここではどのようなコードを出力するのか?

    abstract class Parent {
        static int a = 10;
    
        static {
            System.out.println("Parent init");
        }
    }
    
    class Child extends Parent {
        static {
            System.out.println("Child init");
        }
    }
    
    public class InitializationQuestion2 {
        public static void main(String[] args) {
            System.out.println(Child.a);
        }
    }
    复制代码

    次のように変更してみてください。

    abstract class Parent {
        static final int a = 10;
    
        static {
            System.out.println("Parent init");
        }
    }
    复制代码

    これは、再び次のように変更してみてください。

    abstract class Parent {
        static final int a = value();
    
        static {
            System.out.println("Parent init");
        }
    
        static int value(){
            return 10;
        }
    }
    复制代码

おすすめ

転載: juejin.im/post/5dc0c650f265da4d310743b5