【Java仮想マシンの詳細理解】クラスの初期化プロセス

5660 ワード

クラスの初期化プロセス
クラスのロードプロセス.png
  • ロード
  •       Class                
  • 検証
  •        Class       ,          
  • 準備
  •             ,     ,(         ,  int    0,reference    null)                       。

    ゼロ値.png
  • 解析
  •                 
  • 初期化
  •          static   ,         ,        static      
  • を使用
                ,            ,            ;     
  • アンインストール
  • 実行時に仮想マシンパラメータ+XX:+TraceClassLoadingを加えると、クラスロードの情報が詳細に表示されます.同様にクラスアンインストールの情報を見るには、-XX:TraceClassUnloadingを使用します.
    アクティブリファレンスとパッシブリファレンス
    アクティブリファレンス
  • new,getstatic,putstatic,invokestaticバイトコード命令に遭遇した場合、初期化がなければ
  • を進行的に初期化する
  • 反射の場合、例えば、System.load(「xxxx.xxxx.xx」)
  • は1つのクラスを初期化するが、このクラスの親が初期化していない場合(1つのインタフェースが初期化されている場合、その親インタフェースがすべて初期化される必要はない)
  • .
  • JVMが実行する必要があるプライマリクラス
  • 動的言語サポートに遭遇したとき
  • パッシブリファレンス
  • は、親の または を子によって参照し、親は初期化されません.親の静的プロパティは、子クラスのアクティブな使用ではなく、親クラスのアクティブな使用を表す子クラスによって参照されます.
  • は、タイプの配列を構築することによって、このような
  • を初期化しない.
  • は、あるクラスの通常の明るいタイプを直接参照する場合、そのペアに対して
  • は初期化されません.
    サンプルコード
    class SuperClass {
      public static String msg = "Hello,World";
    
      static {
        System.out.println("SuperClass.static initializer");
      }
    }
    
    class SubClass extends SuperClass {
    
      public static String msg2 = "Hello,World";
    
      static {
        System.out.println("SubClass.static initializer");
      }
    }
  • 子参照親の定数が子
  • を初期化しないことを確認する.
       //                      
        System.out.println(SubClass.msg);
  • 初期化子クラスを検証すると同時に必ず親クラス
  • を初期化する.
        System.out.println(SubClass.msg2);
  • 初期タイプ配列を検証するときは、そのタイプ
  • を初期化しない.
    配列タイプの場合、そのタイプはJVMの実行中に動的に生成され、タイプは[Lxxxx.xxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxxxx.xxxxxxxxxxxx.SubClass;その親タイプはObjectであり、同じ2次元配列タイプは[[Lxxx.xxx.xxx.xxx.xxx
        SubClass[] subClasses = new SubClass[1];

    定数プールの参照
  • ソースコード
  • public class ReferenceExample002 {
    
      public static void main(String[] args) {
        //           System.out.println(Hello, World);
        //              ,       
        System.out.println(ExampleClass.msg);
      }
    }
    
    class ExampleClass {
    
      public static final String msg = "Hello, World";
    
      static {
        System.out.println("ExampleClass.static initializer");
      }
    }
  • コード逆コンパイル後の情報からExampleClassへの直接参照
  • が除去された.
    
    public class ReferenceExample002 {
        public ReferenceExample002() {
        }
    
        public static void main(String[] args) {
            System.out.println("Hello, World");
        }
    }
  • 逆コンパイルReferenceExample 002で得られたアシスト情報は以下の通りである:
  • 逆コンパイルコマンド:javap-c xxx.xxx.xxx
    public class com.zhoutao.example.ReferenceExample002 {
      public com.zhoutao.example.ReferenceExample002();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String Hello, World
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return

    コンパイル中に不確定な定数値が存在するクラスが初期化されます
    package com.zhoutao.classload;
    
    import java.util.UUID;
    
    public class ReferenceExample003 {
    
      public static void main(String[] args) {
    
        System.out.println(ExampleClass.uuid);
      }
    
      static class ExampleClass {
       
        //              
        public static final String uuid = UUID.randomUUID().toString();
    
        static {
          System.out.println("ExampleClass.static initializer");
        }
      }
    }
    

    ExampleClassの静的コードブロックが実行されます:uuidの値はコンパイラでは決定されませんので、対応するクラスを初期化し、コンパイル中に決定された定数値と区別します.
    インタフェースの定数
    1つのインタフェースが初期化されると、親インタフェースが初期化を完了する必要はありません.その後、クラスの初期化時に親クラスが初期化を完了するように要求され、クラスの初期化時に親クラスが初期化を完了するように要求されます.
    public class ReferenceExample005 {
    
      public static void main(String[] args) {
        System.out.println(SubInterface.b);
      }
    
      static interface ParentInterface {
        public static int a = 1;
      }
    
      static interface SubInterface extends ParentInterface {
        public static int b = 2;
      }
    }

    JVMの親インタフェースは、子インタフェースまたは実在クラスの初期化のために初期化されることはなく、 にすぎません.
    プロセスの初期化方法
    Javaはクラスごとにメソッドを生成し、静的変数に対してメソッドを生成します.
    public class ReferenceExample006 {
    
      public static void main(String[] args) {
        ExampleClass exampleClass = ExampleClass.getInstance();
        System.out.println("a = " + ExampleClass.a);
        System.out.println("b = " + ExampleClass.b);
      }
    
      static class ExampleClass {
        public static int a;
    
        public static int b = 0;
    
        private static ExampleClass exampleClass = new ExampleClass();
    
        private ExampleClass() {
          a++;
          b++;
        }
    
        public static ExampleClass getInstance() {
          return exampleClass;
        }
      }
    }

    上記のコードでは、出力値が次のように簡単にわかります.
    a = 1
    b = 1

    ExampleClassの定義bをExampleClassのプライベート構造メソッドの後に配置すると、その出力値は次のようになります.
    a = 1
    b = 0

    これは,メソッドの収集static定義およびコードブロックの場合,順序に従って実行され,プライベート構造メソッドの場合,bに1を付与し,次に public static int b = 0;でbを1と再定義するためである.
    本文はブログ群発一文多発などの運営ツールプラットフォームOpenWriteから発表された.