Javaクラスのロード、リンク、初期化


Javaクラスのロード、リンク、初期化
    Javaバイトコードの表現形式はバイト配列(byte[])であり、JavaクラスのJVMにおける表現形式はjava.lang.Classクラスのオブジェクトである.Javaクラスは、バイトコードからJVMで使用できるようになるまで、ロード、リンク、初期化の3つのステップが必要です.この3つのステップでは、開発者に直接見られるのはJavaクラスのロードであり、Javaクラスローダ(class loader)を使用することで、実行時にJavaクラスを動的にロードすることができます.リンクと初期化はJavaクラスを使用する前に発生するアクションです.Javaクラスのロード、リンク、初期化の手順について詳しく説明します.
1 Javaクラスのロード
    Javaクラスのロードはクラスローダによって行われます.一般的に、クラス・ローダは、起動クラス・ローダとユーザー定義クラス・ローダの2つのクラスに分けられます.両者の違いは、起動クラスローダはJVMのオリジナルコードによって実現され、ユーザーがカスタマイズしたクラスローダはJavaのjava.lang.ClassLoaderクラスから継承されることです.ユーザー定義クラスローダの部分では、一般的にJVMにはいくつかの基本的な実装があります.アプリケーションの開発者は、必要に応じて独自のクラスローダを作成することもできます.JVMで最もよく使われるのは、Javaアプリケーションのロードを開始するためのシステムクラス・ローダです.クラス・ローダ・オブジェクトはjava.lang.ClassLoaderのgetSystemClassLoader()メソッドで取得できます.
    クラスローダが完了する必要がある最終機能は、Javaクラスを定義することです.つまり、JavaバイトコードをJVMのjava.lang.Classクラスのオブジェクトに変換します.しかし、クラス・ロードのプロセスはそれほど簡単ではありません.Javaクラス・ローダには、階層組織構造とエージェント・モードの2つの重要な特徴があります.階層組織構造とは、各クラス・ローダに親クラス・ローダがあり、getParent()メソッドで取得できます.クラスローダは,このような父親−子孫によって組織され,樹状階層を形成する.エージェント・モードとは、Javaクラスの定義作業を自分で完了するか、他のクラス・ローダにエージェントして完了するかのクラス・ローダを指します.エージェント・モードが存在するため、クラスのロード・プロシージャを開始するクラス・ローダと、このクラスを最終的に定義するクラス・ローダは1つではない可能性があります.前者を初期クラスローダと呼び、後者を定義クラスローダと呼ぶ.両方の関連付けは、Javaクラスの定義クラスローダが、クラスがインポートした他のJavaクラスの初期クラスローダであることです.たとえばクラスAがimportを介してクラスBをインポートすると、クラスAの定義クラスローダがクラスBのロードプロセスを開始する.
    
一般的なクラス・ローダは、Javaクラスを自分でロードしようとする前に、まず親クラス・ローダにエージェントされます.親ローダが見つからない場合は、自分でロードしようとします.このロジックはjava.lang.ClassLoaderクラスにカプセル化されています
loadClass()
メソッドの.一般的には、親優先のポリシーで十分です.場合によっては、親ローダにプロキシする前に、自分でロードしようとし、見つからないときにプロキシする逆のポリシーが必要になる場合があります.このやり方はJavaのWebコンテナでよく見られますが、
サーブレット仕様
おすすめの作り方.たとえば、
Apache Tomcat
各Webアプリケーションに独立したクラスローダを提供し、自分が優先的にロードするポリシーを使用します.
IBM WebSphere Application Server
これにより、Webアプリケーションがクラスローダで使用するポリシーを選択できます.
クラスローダの重要な用途の1つは、JVMで同じ名前のJavaクラスに対して独立空間を作成することです.JVMでは、2つのクラスが同じかどうかを判断するには、そのクラスのバイナリ名だけでなく、2つのクラスの定義クラスローダも必要です.両者が全く同じであってこそ,二つのクラスが同じであると考える.したがって,同じJavaバイトコードであっても,2つの異なるクラスローダによって定義された後に得られるJavaクラスは異なる.2つのクラスのオブジェクト間で値付け操作をしようとすると、java.lang.ClassCastExceptionが放出されます.この特性は,同じ名称のJavaクラスがJVM中に存在することに条件を作った.実際のアプリケーションでは、同じ名前のJavaクラスの異なるバージョンがJVMに同時に存在することが要求される場合があります.クラスローダでこのニーズを満たすことができます.この技術はOSGiにおいて広く応用されている.
2 Javaクラスへのリンク
    Javaクラスのリンクとは、JavaクラスのバイナリコードをJVMの実行状態に統合するプロセスです.リンクする前に、このクラスは正常にロードされなければなりません.クラスのリンクには、検証、準備、解析などのいくつかのステップが含まれます.検証は、Javaクラスのバイナリ表現が構造的に完全に正しいことを確認するために使用されます.検証プロセスにエラーが発生した場合、java.lang.VerifyErrorエラーが投げ出されます.準備プロセスは、Javaクラスの静的ドメインを作成し、これらのドメインの値をデフォルト値に設定します.準備プロセスはコードを実行しません.Javaクラスには、親クラス、実装されたインタフェース、メソッドの形式パラメータ、戻り値のJavaクラスなど、他のクラスまたはインタフェースへの形式参照が含まれます.解析のプロセスは、これらの参照されたクラスが正しく見つかることを確保することです.解析のプロセスによって、他のJavaクラスがロードされる可能性があります.
    異なるJVM実装では、異なる解析ポリシーが選択される場合があります.1つの方法は,リンクするときに,すべての依存する形式を再帰的に参照して解析することである.別の方法は、本当に必要なときだけ解析を1つの形式で参照することかもしれません.つまり、Javaクラスが参照されているだけで、実際に使用されていない場合、このクラスは解析されない可能性があります.次のコードを考慮します.
public class LinkTest {   
  public static void main(String[] args) {      
     ToBeLinked toBeLinked = null;      
     System.out.println("Test link.");  
  }
}

    クラスLinkTestはクラスToBeLinkedを参照していますが、実際には使用されていません.変数を宣言しただけで、クラスのインスタンスを作成したり、静的ドメインにアクセスしたりしていません.OracleのJDK 6で、コンパイルしたToBeLinkedのJavaバイトコードを削除してからLinkTestを実行しても、プログラムはエラーを出さない.これは、ToBeLinkedクラスが実際に使用されていないためであり、OracleのJDK 6で使用されているリンクポリシーによってToBeLinkedクラスがロードされないため、ToBeLinkedのJavaバイトコードが実際には存在しないことはわかりません.コードをToBeLinked toBeLinked=new ToBeLinked();その後、同じ方法で運転すると、異常が放出されます.このときToBeLinkedというクラスが本当に使われているので、このクラスをロードする必要があります.
3 Javaクラスの初期化
    Javaクラスが初めて実際に使用されると、JVMはクラスの初期化操作を行います.初期化プロセスの主な動作は、静的コードブロックの実行と静的ドメインの初期化である.クラスが初期化される前に、その直接的な親も初期化する必要があります.ただし、1つのインタフェースの初期化は、親インタフェースの初期化を引き起こすことはありません.初期化時には、ソースコードの上から下の順に静的コードブロックと静的ドメインの初期化が実行されます.次のコードを考慮します.
public class StaticTest {   
  public static int X = 10;  
  public static void main(String[] args) {      
     System.out.println(Y); // 60  
  }  
  static {      
     X = 30;  
  }  
  public static int Y = X * 2;
}

    上のコードでは、初期化時に静的ドメインの初期化と静的コードブロックの実行が上から下へ順次実行されます.したがって、変数Xの値は、まず10に初期化され、その後、30に割り当てられる.変数Yの値は60に初期化される.
    Javaクラスとインタフェースの初期化は、次のような特定のタイミングでのみ発生します.
  • Javaクラスのインスタンスを作成します.のように   
    MyClass obj = new MyClass()
       
  • Javaクラスの静的メソッドを呼び出します.のように   
    MyClass.sayHello()
       
  • は、Javaクラスまたはインタフェースで宣言された静的ドメインに値を割り当てます.のように   
    MyClass.value = 10
       
  • は、Javaクラスまたはインタフェースで宣言された静的ドメインにアクセスし、このドメインは定数変数ではありません.のように   
    int value = MyClass.value
       
  • 最上位Javaクラスでassert文を実行します.

  •     Java反射APIによってクラスやインタフェースが初期化されることもあります.Javaクラスまたはインタフェースの静的ドメインにアクセスすると、このドメインを本当に宣言するクラスまたはインタフェースのみが初期化されます.次のコードを考慮します.
    class B {   
      static int value = 100;  
      static {      
         System.out.println("Class B is initialized."); //  
      }
    }
    class A extends B {  
      static {      
         System.out.println("Class A is initialized."); //  
      }
    }
    public class InitTest {  
      public static void main(String[] args) {      
         System.out.println(A.value); // 100  
      }
    }

        上記のコードでは、クラスInitTestは、クラスBで宣言された静的ドメインvalueをA.valueで参照する.valueはクラスBで宣言されるため、クラスBのみが初期化され、クラスAは初期化されません.
    4独自のクラス・ローダの作成
        Javaアプリケーション開発では、アプリケーション独自のクラスローダを作成する必要がある場合があります.典型的なシーンには、特定のJavaバイトコードの検索方法、バイトコードの暗号化/復号化、同名Javaクラスの分離などが含まれる.独自のクラス・ローダを作成するのは複雑なことではありません.java.lang.ClassLoaderクラスから継承し、対応するメソッドを上書きするだけです.JAva.lang.ClassLoaderでは、クラスローダを作成する際に考慮すべきいくつかの方法について説明します.
  • defineClass():Javaバイトコードのバイト配列からjava.lang.Classへの変換を完了するために使用されます.この方法は上書きできないが,一般的には原生コードで実現される.
  • findLoadedClass():このメソッドは、ロードされたJavaクラスを名前に基づいて検索するために使用されます.1つのクラス・ローダでは、同じ名前のクラスは繰り返しロードされません.
  • findClass():このメソッドは、名前に基づいてJavaクラスを検索してロードするために使用されます.
  • loadClass():このメソッドは、名前に基づいてJavaクラスをロードするために使用されます.
  • resolveClass():このメソッドはJavaクラスをリンクするために使用されます.

  •     ここで比較的紛らわしいのがfindClass()メソッドとloadClass()メソッドの役割である.前述したように、Javaクラスのリンク中にJavaクラスを解析する必要があり、解析により現在のJavaクラスが参照する他のJavaクラスがロードされる可能性があります.このとき、JVMは、現在のクラスの定義クラスローダのloadClass()メソッドを呼び出すことで、他のクラスをロードします.findClass()メソッドは、作成したクラスローダの拡張点を適用します.独自のクラスローダを適用するには、findClass()メソッドを上書きして、カスタムクラスロードロジックを追加する必要があります.loadClass()メソッドのデフォルトインプリメンテーションはfindClass()メソッドの呼び出しを担当します.
        前述したように、クラス・ローダのエージェント・モードでは、親優先のポリシーがデフォルトで使用されます.この戦略の実装はloadClass()メソッドにカプセル化されている.このポリシーを変更するには、loadClass()メソッドを上書きする必要があります.
        次のコードは、カスタムクラスロードの一般的な実装モードを示します.
    public class MyClassLoader extends ClassLoader {   
      protected Class<?> findClass(String name) throws ClassNotFoundException {      
         byte[] b = null; // Java      
     return defineClass(name, b, 0, b.length);  
      }
    }