ホームページ  >  記事  >  Java  >  Java学習のためのJvmクラスロードメカニズム

Java学習のためのJvmクラスロードメカニズム

青灯夜游
青灯夜游転載
2018-10-16 16:35:252025ブラウズ

この記事では、Java学習におけるJvmクラスの読み込みの仕組みを紹介します。困っている友人は参考にしていただければ幸いです。

1. 概要

仮想マシンはクラス ファイル (バイナリ バイト ストリーム) をメモリにロードし、データを検証、変換、解析、初期化し、最終的に次のようなファイルを形成します。 Java タイプでは、この一連のプロセスがクラスのロード メカニズムとして直接使用されます。

2. クラスのロードのタイミング

クラスは、仮想マシンによるメモリへのロードから開始され、メモリからアンロードされるまでの ライフ サイクル 全体には次のものが含まれます。 読み込み - - 検証 - 準備 - 解析 - 初期化 - 使用 - アンインストール この 7 段階です。検証、準備、解析の 3 つの部分をまとめて接続と呼びます。

ライフサイクル図は次のとおりです。

ロード、検証、準備、初期化、アンロードの 5 つのフェーズの順序が決定され、クラスのロード プロセスは、この順序で開始する必要があります。解析フェーズは必ずしもこの順序で開始する必要はありません。場合によっては、初期化後に開始することもできます。これは、Java 言語の動的バインディングをサポートするためでもあります。

クラスの初期化フェーズはどのような状況でトリガーされる可能性がありますか? (前提:ロード、検証、準備は当然実行されている)

  1. new、getstatic、putstatic、invokestaticの4つの命令に遭遇した場合、クラスが初期化されていない場合は初期化(これら 4 つの命令が動作する最も一般的なシナリオは、オブジェクトの新しいインスタンス化、クラスの静的フィールドの読み取りまたは設定 (最終的な変更または定数プールに入れられた静的フィールドを除く)、static の呼び出しです。

  2. リフレクションを使用する場合

  3. クラスを初期化するとき、その親クラスがまだ初期化されていない場合は、次のことを行う必要があります。最初に親クラスの初期化をトリガーします

  4. 仮想マシンの起動時に、実行するメイン クラス (仮想マシンが初期化されるメイン メソッドを含むクラス) を指定する必要があります。このクラスを最初に使用します

  5. #jdk1.7 動的言語がサポートされている場合、java.lang.invoke.MethodHandle インスタンスの最終解析結果が REF_getStatic、REF_putStatic、REF_invokeStatic のメソッド ハンドルの場合に使用します。このハンドルに対応するクラスが初期化されていない場合は、最初にその初期化をトリガーする必要があります。

注: クラスを参照するすべてのメソッドが初期化をトリガーするわけではありません (たとえば、パッシブ参照)。 : 配列の作成、親クラスを参照するサブクラスの最終変更された変数と静的変数の参照は、サブクラスの初期化をトリガーしませんが、親クラスの初期化をトリガーします

#3。クラス読み込みプロセス

#- Loading##。

# ロードはクラスのロード段階であり、仮想マシンは次の 3 つのことを完了する必要があります。

このクラスを定義するバイナリ バイト ストリームを取得します。クラスの完全修飾名

  1. このバイト ストリームで表される静的ストレージ構造をメソッド領域の実行時データ構造に変換します

  2. Generateメモリ内のこの型の表現 java.lang.Object オブジェクトは、メソッド領域でこのクラスのさまざまなデータへのアクセス ポイントとして機能します。

  3. クラス読み込みの他の段階と比較すると、ロード段階 (正確には、クラスのバイナリ バイト ストリームを取得するロード段階のアクション) は、開発者が最も制御できる段階です。ロードフェーズは、システムが提供するブートクラスローダーを使用して完了することも、開発者のカスタムクラスローダー(つまり、クラスローダーのloadClass()メソッドをオーバーライドすること)によって完了することもできるためです。

  4. ロードが完了すると、外部バイナリ バイト ストリームが仮想マシンに必要な形式に変換されてメソッド領域に格納され、java.lang.Class クラスのオブジェクトがインスタンス化されます。メモリ。このオブジェクトは、プログラムがメソッド領域内のこれらのタイプのデータにアクセスするための外部インターフェイスとして機能します。

ロードフェーズの一部と接続フェーズが交互に行われており、ロードが完了するまで検証などの操作を行うことができません。ロード中に挟まれたこれらのアクションは引き続き接続フェーズに属し、これら 2 つのフェーズの開始時間は依然として固定シーケンスを維持します。

-検証

検証は、ロードされたバイナリ バイト ストリームに含まれる情報が仮想マシンの仕様に準拠していることを確認するための接続の最初のステップです。

検証フェーズは、大きく次の 4 つの検証アクションに分かれます。

ファイル形式の検証

: バイト ストリームがクラス ファイル形式の仕様に準拠しているかどうかを検証します。たとえば、マジックナンバー 0xCAFEBABE で始まるかどうか、メジャー バージョン番号とマイナー バージョン番号が現在の仮想マシンの処理範囲内にあるかどうか、定数プール内の定数にサポートされていない型があるかどうかなどです。

メタデータ検証: バイトコードで記述された情報に対してセマンティック分析を実行します。例: このクラスには親クラスがあるかどうか、また親クラスを正しく継承しているかどうか。

バイトコード検証: データ フローと制御フローの分析を通じて、プログラムのセマンティクスが正当かつ論理的であることを判断します (端的に言うと、クラスのメソッド本体を分析して、この方法が実行時に仮想マシンに害を及ぼさないことを確認してください)。

シンボル参照の検証: 解析アクションが正常に実行できることを確認します。

検証フェーズは非常に重要ですが、必ずしも必要なフェーズではありません (プログラムの実行時間には影響しないため)。実行中のすべてのコードが繰り返し使用され検証されている場合は、実装フェーズ中に -Xverify:none パラメーターを使用して検証をオフにすることができます。

-準備

クラス変数に正式にメモリを割り当て、クラス変数の初期値を設定します。これらの変数によって使用されるメモリはメソッド領域に割り当てられます。

注:

  • 現時点では、静的変数のみが割り当てられ、インスタンス変数はオブジェクト インスタンスと一緒に割り当てられません。 Java ヒープ内

  • #初期値は通常、データ型のゼロ値です。静的変数 public static int value = 123; を定義すると、準備段階では value の初期値は 123 ではなく 0 になります。

  • #final によって変更された変数は、準備段階で属性によって指定された値に初期化されます。 例: public static Final int value = 123; の場合、準備フェーズの value の初期値は 123 です。

- 解析

解析フェーズは、仮想マシンが定数プール内のシンボル参照を直接参照に置き換えるプロセスです。 。解析アクションは主に、クラスまたはインターフェイス、フィールド、クラス メソッド、インターフェイス メソッド、メソッド タイプ、メソッド ハンドル、および呼び出しサイト修飾子の 7 種類のシンボル参照に対して実行されます。

シンボル参照: シンボルのセットを使用して、参照のターゲットを記述します。シンボルには、任意の形式のリテラルを使用できます。

直接参照: ターゲットへのポインタ、相対オフセット、またはターゲットを間接的に見つけることができるハンドル。

-初期化

初期化フェーズは、クラス コンストラクターの () メソッドを実行するプロセスです。準備フェーズでは、システムに必要な初期値が変数に割り当てられ、初期化フェーズでは、プログラマが設定したパラメータ値に従ってクラス変数やその他のリソースが初期化されます。

クラス コンストラクター() メソッド: このメソッドは、クラス内のすべてのクラス変数の代入アクションを自動的に収集し、静的コード ブロック内のステートメントをマージするコンパイラーによって生成されます。

コンパイラが収集する順序は、ソース ファイル内でステートメントが出現する順序によって決まります。静的コード ブロックは、静的ブロックの前に定義された変数、静的ブロックの後に定義された変数、および静的ブロックで定義された変数にのみアクセスできます。前の静的ブロックに値を割り当てることはできますが、アクセスすることはできません。

非法向前引用示例

public class SuperClass {
    public static int va;
    static {
        value = 1;            //可以编译通过
        va = value;           //报错  非法向前引用
        System.out.println("父类初始化");
    }

    public static int value = 123;
}

() メソッドは、クラスまたはインターフェイスには必要ありません。クラスに静的コード ブロックがなく、変数への代入操作がない場合、コンパイラはこれを行う必要はありません。クラス生成 メソッド

静的ブロックはインターフェイスでは使用できませんが、変数割り当て操作は実行できるため、インターフェイスとクラスは メソッドを生成します。違いは、インターフェイスの初期化では、最初に親クラスを初期化する必要がないことです。親インターフェイスの初期化は、親インターフェイスの変数が使用される場合にのみトリガーされます。さらに、インターフェイスの実装クラスはインターフェイスのインスタンス化をトリガーしません。

仮想マシンは、クラスの () メソッドが複数のスレッドで正しくロックされ、同期されるようにします。複数のスレッドがクラスを初期化する場合、そのクラスは 1 つのスレッドだけが実行されます。 () メソッドの場合、他のスレッドは待機状態になります。アクティブなスレッドのみが実行を完了できます。クラスの () メソッドに長時間実行される操作がある場合、複数のスレッドがブロックされる可能性があります。実際のアプリケーションでは、このブロックは非常に隠されていることがよくあります。

4. クラスローダー

仮想マシン設計チームは、「クラスの完全修飾名を通じてこのクラスを記述するバイナリ バイト ストリームを取得する」というアクションをクラス ローディングに Java に実装しました。これにより、アプリケーションは必要なクラスを取得する方法を決定できるようになります。このアクションを実装するコードのブロックは、クラス ローダーと呼ばれます。

Java 開発者の観点から見ると、クラス ローダーは次の 3 つのタイプに大別されます。

ブートストラップ クラスローダー (ブートストラップ クラスローダー): にファイルを保存します。 ;\lib (Javahome は jdk インストール ディレクトリです) ディレクトリ、または -Xbootclasspath パラメータで指定されたパスにあり、仮想マシンによって認識されます (rt.jar などのファイル名によってのみ認識され、名前は一致しません)クラス ライブラリは lib 配下に配置してもロードされません) クラス ライブラリは仮想マシンのメモリにロードされます。 スタートアップ クラス ローダーは、Java プログラムから直接使用することはできません。

拡張クラスローダー: このローダーは、\lib\ext ディレクトリ、またはディレクトリ内のすべてのクラス ライブラリをロードする役割を担う sun.misc.Launcher$ExtClassLoader によって実装されます。 java.ext.dirs システム変数で指定されたシステム パス。開発者は、拡張クラス ローダーを直接使用できます。

アプリケーション クラスローダー: このローダーは sun.misc.Launcher$AppClassLoader によって実装され、ユーザー クラス パス (ClassPath) で指定されたクラス ライブラリをロードします。開発者はこのローダーを直接使用できます。アプリケーションにカスタム クラス ローダーがない場合、これがプログラムによってデフォルトで実行されるクラス ローダーになります。 (システムローダー)

我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。

这些类加载器之间的关系如下图:

 

5.双亲委派模型: 

双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。

双亲委派机制:

1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

ClassLoader源码分析:    

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先检查此类是否已被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //委派给父类加载器去加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果没有父加载器,则调用启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //如果父加载器无法加载,则调用本身加载器去加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }                                         

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行

参考

《深入理解Java虚拟机》 

总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问Java视频教程java开发图文教程bootstrap视频教程

以上がJava学習のためのJvmクラスロードメカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcnblogs.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。