JavaRush /Java Blog /Random-JA /クラスが JVM にロードされる仕組み
Aleksandr Zimin
レベル 1
Санкт-Петербург

クラスが JVM にロードされる仕組み

Random-JA グループに公開済み
プログラマの作業の中で最も難しい部分が完了し、「Hello World 2.0」アプリケーションが作成されたら、残るのは配布キットを組み立てて顧客、または少なくともテスト サービスに転送することだけです。ディストリビューションではすべてが正常に動作しており、プログラムを起動すると Java 仮想マシンが登場します。仮想マシンがクラス ファイルにバイトコードの形式で示されたコマンドを読み取り、プロセッサへの命令として変換することは周知の事実です。仮想マシンに入るバイトコードのスキームについて少し理解することをお勧めします。

クラスローダー

これは、コンパイルされたバイトコードを JVM に提供するために使用され、通常は拡張子 のファイルに保存されます.classが、ネットワーク経由でダウンロードしたり、アプリケーション自体によって生成したりするなど、他のソースから取得することもできます。 JVM でのクラスのロード方法 - 1Java SE 仕様によれば、JVM でコードを実行するには、次の 3 つの手順を完了する必要があります。
  • リソースからバイトコードをロードし、クラスのインスタンスを作成するClass

    これには、以前にロードされたクラスの中から要求されたクラスを検索すること、Classロード用のバイトコードを取得してその正しさを確認すること、クラスのインスタンスを作成すること (実行時に操作するため)、親クラスをロードすることが含まれます。親クラスとインターフェイスがロードされていない場合、問題のクラスはロードされていないとみなされます。

  • バインディング (またはリンク)

    仕様によれば、このステージはさらに 3 つのステージに分かれています。

    • 検証: 受信したバイトコードの正当性がチェックされます。
    • 準備、静的フィールドに RAM を割り当て、それらをデフォルト値で初期化します (この場合、明示的な初期化があれば、初期化段階ですでに行われます)。
    • Resolution、型、フィールド、メソッドのシンボリック リンクの解決。
  • 受信したオブジェクトを初期化しています

    ここでは、前の段落とは異なり、何が起こるべきかすべてが明らかであるように見えます。もちろん、これがどのようにして起こるのかを正確に理解することは興味深いでしょう。

これらすべての手順は、次の要件に従って順次実行されます。
  • クラスはリンクされる前に完全にロードされている必要があります。
  • クラスは初期化する前に完全にテストして準備する必要があります。
  • リンク解決エラーは、リンク段階で検出された場合でも、プログラムの実行中に発生します。
ご存知のとおり、Java はクラスの遅延 (または遅延) ロードを実装します。これは、ロードされたクラスの参照フィールドのクラスのロードは、アプリケーションがそれらへの明示的な参照に遭遇するまで実行されないことを意味します。つまり、シンボリック リンクの解決はオプションであり、デフォルトでは行われません。ただし、JVM 実装では、精力的なクラスローディングを使用することもできます。すべてのシンボリック リンクは直ちに考慮される必要があります。最後の要件が適用されるのはこの点です。シンボリック リンクの解決はクラス読み込みのどの段階にも関連付けられていないことにも注目してください。一般に、これらの各段階は良い研究になります。最初の段階、つまりバイトコードのロードを理解してみましょう。

Java ローダーの種類

Java には 3 つの標準ローダーがあり、それぞれが特定の場所からクラスをロードします。
  1. Bootstrapは基本的なローダーであり、Primordial ClassLoader とも呼ばれます。

    rt.jar アーカイブから標準の JDK クラスをロードします

  2. 拡張クラスローダー- 拡張ローダー。

    拡張クラスをロードします。拡張クラスはデフォルトでは jre/lib/ext ディレクトリにありますが、java.ext.dirs システム プロパティによって設定できます。

  3. システム クラスローダー– システム ローダー。

    CLASSPATH 環境変数で定義されたアプリケーション クラスをロードします。

Java はクラス ローダーの階層を使用します。当然、ルートがベースになります。次に拡張ローダー、そしてシステム ローダーが続きます。当然のことながら、各ローダーは、それ自体が実行できない場合にロードを委任できるように、親へのポインターを保存します。

抽象クラス ClassLoader

基本ローダーを除く各ローダーは、抽象クラスの子孫ですjava.lang.ClassLoader。たとえば、拡張ローダーの実装は クラス でsun.misc.Launcher$ExtClassLoader、システム ローダーは ですsun.misc.Launcher$AppClassLoader。ベース ローダーはネイティブであり、その実装は JVM に含まれています。拡張するクラスは、java.lang.ClassLoaderブラックジャックやこれらと同じクラスをロードする独自の方法を提供できます。これを行うには、対応するメソッドを再定義する必要がありますが、現時点では表面的にしか検討できません。この問題については詳しく理解できませんでした。どうぞ:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)数少ないパブリック メソッドの 1 つで、クラスをロードするためのエントリ ポイントです。loadClass(String name, boolean resolve)その実装は要約すると、オーバーライドする必要がある別の保護されたメソッドを呼び出すことになります。この保護されたメソッドの Javadoc を見ると、次のようなことがわかります。2 つのパラメーターが入力として提供されています。1 つは、ロードする必要があるクラスのバイナリ名 (または完全修飾クラス名) です。クラス名はすべてのパッケージのリストで指定されます。2 番目のパラメーターは、シンボリック リンクの解決が必要かどうかを決定するフラグです。デフォルトではfalseで、遅延クラスローディングが使用されることを意味します。さらに、ドキュメントによると、メソッドのデフォルト実装では、findLoadedClass(String name)クラスが以前にロードされているかどうかを確認し、ロードされている場合はこのクラスへの参照を返す呼び出しが行われます。それ以外の場合は、親ローダーのクラスロードメソッドが呼び出されます。どのローダーもロードされたクラスを見つけることができなかった場合、各ローダーは逆の順序で、そのクラスを見つけてロードし、 をオーバーライドしようとしますfindClass(String name)。これについては、「クラスローディングスキーム」の章で詳しく説明します。最後に、クラスがロードされた後、解決フラグに応じて、シンボリック リンクを介してクラスをロードするかどうかが決定されます。わかりやすい例は、クラスの読み込みステージ中に解決ステージを呼び出すことができることです。したがって、クラスを拡張しClassLoader、そのメソッドをオーバーライドすることにより、カスタム ローダーは仮想マシンにバイトコードを配信するための独自のロジックを実装できます。Java は、「現在の」クラス ローダーの概念もサポートしています。現在のローダーは、現在実行中のクラスをロードしたローダーです。各クラスはどのローダーでロードされたかを認識しており、その を呼び出すことでこの情報を取得できますString.class.getClassLoader()。すべてのアプリケーション クラスでは、通常、「現在の」ローダーはシステム ローダーです。

クラスローディングの 3 つの原則

  • 代表団

    クラスをロードするリクエストは親ローダーに渡され、親ローダーがクラスを見つけてロードできなかった場合にのみ、クラス自体のロードが試行されます。このアプローチにより、基本ローダーにできるだけ近いローダーを使用してクラスをロードできます。これにより、クラスの可視性が最大限に高まります。各ローダーは、ロードされたクラスの記録を保持し、それらをキャッシュに置きます。これらのクラスのセットはスコープと呼ばれます。

  • 可視性

    ローダーは「その」クラスと「親」のクラスだけを認識し、「子」によってロードされたクラスについては知りません。

  • 独自性

    クラスは 1 回だけロードできます。委任メカニズムにより、クラスのロードを開始するローダーが、以前に JVM にロードされたクラスをオーバーロードしないことが保証されます。

したがって、ブートローダーを作成するとき、開発者はこれら 3 つの原則に従う必要があります。

クラスローディングスキーム

クラスをロードする呼び出しが発生すると、このクラスは現在のローダーのすでにロードされているクラスのキャッシュ内で検索されます。目的のクラスが以前にロードされていない場合、委任の原則により、階層の 1 つ上のレベルにある親ローダーに制御が移されます。親ローダーも、キャッシュ内で目的のクラスを見つけようとします。クラスがすでにロードされており、ローダーがその場所を知っている場合は、Classそのクラスのオブジェクトが返されます。そうでない場合は、ベース ブートローダーに到達するまで検索が続行されます。ベースローダーが必要なクラスに関する情報を持っていない場合 (つまり、まだロードされていない場合)、このクラスのバイトコードは、指定されたローダーが知っているクラスの場所で検索されます。ロードされると、制御は子ローダーに戻り、子ローダーは既知のソースからロードしようとします。上で述べたように、ベース ローダーのクラスの場所は rt.jar ライブラリ、拡張ローダーの場合は jre/lib/ext 拡張子を持つディレクトリ、システムのクラスの場所は CLASSPATH、ユーザーのクラスの場所は別のものにすることができます。 。したがって、クラスのロードの進行は逆方向、つまりルート ローダーから現在のローダーへ進みます。クラスのバイトコードが見つかると、クラスが JVM にロードされ、その型のインスタンスが取得されますClass。簡単にわかるように、説明した読み込みスキームは、上記のメソッドの実装に似ていますloadClass(String name)。以下にこの図を示します。
JVM でのクラスのロード方法 - 2

結論として

言語学習の最初のステップでは、Java でクラスがどのようにロードされるかを特に理解する必要はありませんが、これらの基本原則を知っていれば、 や などのエラーが発生したときに絶望することを避けることができClassNotFoundExceptionますNoClassDefFoundError。まあ、少なくとも問題の根本が何であるかを大まかには理解できます。ClassNotFoundExceptionしたがって、プログラムの実行中にクラスが動的にロードされるとき、ローダーがキャッシュ内またはクラスパスに沿って必要なクラスを見つけられない場合、例外が発生します。ただし、このエラーNoClassDefFoundErrorはより重大で、必要なクラスがコンパイル中に利用可能であったが、プログラムの実行中に表示されなかった場合に発生します。これは、プログラムが使用するライブラリをインクルードするのを忘れた場合に発生する可能性があります。そうですね、仕事で使用するツールの構造原理を理解するという事実自体 (ツールの奥深くまで明確かつ詳細に浸る必要はありません) によって、このメカニズムの内部で発生するプロセスの理解がさらに明確になります。順番に、このツールを自信を持って使用することにつながります。

情報源

Java での ClassLoader の仕組み 全体として、アクセスしやすい情報を提供する非常に役立つソースです。 クラスのロード、ClassLoader かなり長い記事ですが、これらと同じものを使用して独自のローダー実装を作成する方法に重点を置いています。 ClassLoader: クラスの動的ロード 残念ながら、このリソースは現在利用できませんが、クラスロードスキームを示す最もわかりやすい図を見つけたので、追加せずにはいられません。 Java SE 仕様: 第 5 章 ロード、リンク、および初期化
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION