Java JVM: 仮想マシン クラスのロード メカニズム (5)

  Java 仮想マシンは、クラスを記述するデータを Class ファイルからメモリにロードし、データを検証、変換、解析、初期化して、最終的に仮想マシンが直接使用できる Java 型を形成します。

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

  • ライフサイクル全体では、ロード、検証、準備、解析、初期化、使用、アンロードが行われます。
  • ロード、検証、準備、初期化、アンロードの順序は決定的であり、解析フェーズは決定的ではありません。
    • 場合によっては、Java の動的バインディングをサポートするために、初期化フェーズの後に開始することもできます。
  • 初期化
    • new キーワードがオブジェクトをインスタンス化するとき
    • 型の静的フィールドの読み取りまたは設定
    • ある型の静的メソッドを呼び出す場合
    • 型に対してリフレクション呼び出しを行う場合
    • クラスが初期化されるとき、親クラスは初期化されていません
    • 仮想マシンの起動時に実行するメインクラス(mainメソッドを含む)を指定する必要があります
  • 静的フィールドの場合、このフィールドを直接定義するクラスのみが初期化されます。
    • 親クラスで定義された静的フィールドをサブクラス経由で参照すると、親クラスの初期化のみがトリガーされ、サブクラスの初期化はトリガーされません。
  • インターフェイスの初期化プロセス
    • クラスが初期化されるとき、そのすべての親クラスが初期化されますが、インターフェイスが初期化されるとき、そのすべての親インターフェイスが初期化されている必要はありません。
      • 親インターフェイスが使用される場合 (インターフェイスで定義された定数) にのみ初期化されます。

2、クラスロードのプロセス

つまり、ロード、検証、準備、解析、初期化の 5 つの段階です。

2.1 読み込み

  • 終わったこと
    • このクラスを定義するバイナリ バイト ストリームを完全修飾名で取得します。
    • ZIP アーカイブ、JAR、EAR、WAR 形式で利用可能
    • ネットワークからの取得(Webアプレット)、実行時計算生成(リフレクション)、データベース取得、暗号化ファイル取得
    • このバイト ストリームで表される静的ストレージ構造をメソッド領域の実行時データ構造に変換します。
    • このクラスを表す java.lang.Class オブジェクトを、メソッド領域内のこのクラスのさまざまなデータへのアクセス エントリとしてメモリ内に生成します。
  • ロード段階は、Java 仮想マシンの組み込みブートストラップ クラス ローダーを使用するか、ユーザー定義のクラス ローダー (クラス ローダーの findClass() または loadClass() を書き換える) によって完了できます。
  • 配列クラス自体はクラスローダーを通じて作成されるのではなく、Java 仮想マシンによってメモリ内に直接かつ動的に構築されます。
    • 参照型の場合は、定義された読み込みプロセスを再帰的に使用して、このコンポーネント型を読み込みます。
    • 参照型ではない場合、仮想マシンは配列をブートストラップ クラス ローダーの関連付けとしてマークします。
  • 型データがメソッド領域に配置されると、クラス java.lang.Class のオブジェクトが、プログラムがメソッド領域の型データにアクセスするための外部インターフェイスとして Java ヒープ メモリ内にインスタンス化されます。
  • 読み込みフェーズと接続フェーズのアクションの一部 (バイトコード ファイル形式検証アクションの一部など) がインターリーブされます。
  • 現地語
    • クラスの完全修飾名を使用して、このクラスのバイナリ バイト ストリームを取得し、このバイト ストリームの静的ストレージ構造をメソッド領域の実行時データ構造に変換し、最後にメモリ内に java.lang.Class オブジェクトを生成します。 , メソッド領域のこのクラスのデータ アクセス エントリとして

2.2 検証

  • クラス ファイルのバイト ストリームに含まれる情報が仕様のすべての要件を満たしていることを確認し、仮想マシンのセキュリティが損なわれないようにします。
    • 例: 配列の境界外のデータへのアクセス、実装されていない型への変換、存在しないコードへのジャンプ
    • エラーや悪意のあるバイトコード ストリームの読み込みにより、システム全体が攻撃されたり、クラッシュしたりする可能性が非常に高くなります。
  • 4 段階の検査アクション: ファイル形式、メタデータ、バイトコード、シンボル参照
    • ファイル形式の検証
      • マジックナンバー 0xCAFEBABE で始まるかどうか、メジャー バージョン番号とマイナー バージョン番号が Java 仮想マシンで受け入れられる範囲内であるかどうか、定数がサポートされていない定数タイプであるかどうか
  • メタデータ検証、セマンティック分析
    • 親クラスが存在するかどうか、親クラスが継承を許可されていないクラスを継承しているかどうか、親クラスまたは親インターフェイスに実装する必要があるすべてのメソッドを実装しているかどうか
  • バイトコード検証
    • データフロー分析と制御フロー分析を通じて、セマンティクスが正当かつ論理的であると判断され、クラスのメソッド本体を検証および分析する必要がある
    • オペランド スタックのデータ型と命令コード シーケンスがいつでも連携できるようにします (int 型は long 型としてロードされます)。
    • ジャンプ命令がメソッド本体の外側のバイトコード命令にジャンプしないようにしてください。
    • メソッド本体の型変換が常に有効であることを確認します (親クラスのオブジェクトがサブクラスのデータ型に割り当てられます)。
  • シンボリック参照の検証
    • シンボル参照の検証は、クラス自体以外のさまざまな情報の一致チェックとして見ることができます。
      • 人気:依存する一部の外部クラス、メソッド、フィールド、その他のリソースが欠落しているか、アクセスが禁止されているか
    • 内容を確認する
      • シンボル参照内の文字列で記述された完全修飾名で、対応するクラスを見つけることができるかどうか
      • メソッドのフィールドの説明と一致するメソッドとフィールド、および指定されたクラス内の単純名で記述されたメソッドとフィールドが存在するかどうか
      • シンボリック参照内のクラス、フィールド、およびメソッドのアクセシビリティに現在のクラスがアクセスできるかどうか

2.3 準備

  • 準備段階は、クラスで定義された変数 (つまり、静的変数、静的によって変更された変数) に正式にメモリを割り当て、クラス変数の初期値 (初期値はゼロ値) を設定する段階です。
  • 現時点では、メモリ割り当てにはクラス変数のみが含まれ、インスタンス変数は含まれません。インスタンス変数は、初期化中にオブジェクトとともに Java ヒープに割り当てられます。

2.4 分析

  • Java 仮想マシンが定数プール内のシンボリック参照を直接参照に置き換えるプロセス
    • シンボリック参照: 参照されるターゲットを説明するために一連のシンボルが使用されます。シンボルは、曖昧さなくターゲットを見つけるために使用できる限り、任意の形式のリテラル値にすることができます。
    • 直接参照: 直接参照は、ターゲットを直接指すことができるポインター、相対オフセット、またはターゲットを間接的に見つけることができるハンドルです。
  • クラスまたはインターフェイスの分析 (クラスは D、シンボル参照は N、クラスまたはインターフェイスは C)
    • C が配列型でない場合、仮想マシンは N を表す完全修飾名を D のクラス ローダーに渡し、クラス C をロードします。
    • C が配列型で、配列要素の型がオブジェクトの場合、N の記述子は "[Ljava/lang/Integer" の形式になります。
    • 上記 2 つの手順に異常がなければ、C は仮想マシン内で実際に有効なクラスまたはインターフェイスになっています。
  • フィールド解析
    • まず、フィールド テーブルの class_index 項目でインデックス付けされた CONSTANT_Class_info シンボル参照 (フィールドが属するクラスまたはインターフェイスのシンボル参照) を分析します。
    • 解析が成功した場合は、C を使用してこのフィールドが属するクラスまたはインターフェイスを表し、後続のフィールドの検索を続けます。
      • C 自体に、単純名とフィールド記述子の両方がターゲットに一致するフィールドが含まれている場合は、フィールドの直接参照を直接返します。そうでない場合は、継承関係に従って、各インターフェイスとその親インターフェイスまたは親クラスを下から上に再帰的に検索します。
      • 検索プロセスで参照が正常に返され、このフィールドの権限検証が実行されます。
  • メソッド分析
    • まず、メソッド テーブルの class_index 項目でインデックス付けされたメソッドが属するクラスまたはインターフェイスのシンボリック参照を解決します。
    • 追跡方法の検索
      • Class ファイル形式では、クラスメソッドとインターフェースのメソッドシンボル参照の定数型定義が分離されており、class_index のインデックス C がメソッドテーブルのインターフェースである場合は例外がスローされます。
      • 最初のステップでは、単純名と記述子の両方がターゲットに一致するメソッドがクラス内に存在するかどうかを確認し、存在する場合は返します。
      • それ以外の場合は、クラスの親クラスを再帰的に検索し、クラスとその親インターフェイスによって実装されたインターフェイスのリストを再帰的に検索します。
      • それ以外の場合は失敗します
  • 界面法解析
    • インターフェイス メソッド テーブルの class_index 項目でインデックス付けされたメソッドが属するクラスまたはインターフェイスへのシンボリック参照を解決します。
    • 後続のインターフェースメソッド検索
      • class_index のインデックス C がインターフェイスではなくクラスであることがインターフェイス メソッド テーブルで見つかった場合、例外がスローされます。
      • それ以外の場合は、インターフェイスを調べて、単純な名前とターゲットに一致する記述子の両方を持つメソッドがあるかどうかを確認します。
      • それ以外の場合は、インターフェースの親インターフェースを再帰的に検索します。
      • Java のインターフェイスでは多重継承が可能です。ターゲットに一致する C の異なる親インターフェイスに複数の単純な名前と記述子がある場合は、そのうちの 1 つを返して検索を終了します。
      • それ以外の場合は失敗します

2.5 初期化

  • Java 仮想マシンは実際にクラス内に記述された Java プログラム コードの実行を開始し、アプリケーション プログラムに主導権を渡します。
  • 初期化フェーズでは、プログラマがコーディングを通じて開発した主観的な計画に従って、クラス変数やその他のリソースを初期化します。
    • 初期化フェーズは、クラスのコンストラクター () メソッドを実行するプロセスです。
  • () メソッドは、コンパイラによって自動的に収集されたクラス内のすべてのクラス変数の代入アクションと、静的ステートメント ブロック (static{} ブロック) の要約ステートメントの組み合わせによって生成され、順序は次の順序によって決まります。外観
  • () メソッドとクラス コンストラクターは、親クラス コンストラクターを明示的に呼び出す必要はありません。Java 仮想マシンは、サブクラスが実行される前に親クラスが実行されていることを保証します。
  • インターフェイスでは静的ステートメント ブロックを使用できませんが、変数初期化のための代入操作はまだあります。インターフェイスの () メソッドは最初に親インターフェイス () メソッドを実行する必要はなく、使用されたときにのみ初期化されます。

3. クラスローダー

3.1 クラスとクラスローダー

  • クラスロードアクションを実装するためにのみ使用され、各クラスローダーは独立したクラス名前空間を持ちます。
  • 異なるクラスローダーによってロードされたクラス。これら 2 つのクラスは等しくなくてはなりません

3.2 親の委任モデル

  • 仮想マシンの観点から見ると、異なるクラス ローダーは 2 つだけです。起動クラス ローダー (Bootstrap ClassLoader) とその他すべてのクラス ローダーです。
  • JDK 1.2 以降、Java は 3 層のクラスローダー、親委任クラスのロード構造を維持してきました。
  • ほとんどの Java プログラムは、システムが提供する次の 3 つのクラス ローダーを使用してロードされます。
    • スタートアップ クラス ローダー: /lib ディレクトリへのロードと保存を担当します。Java プログラムは直接呼び出すことができないため、処理のためにブート クラス ローダーに委任する必要があります。
    • 拡張クラス ローダー: /lib/ext ディレクトリまたは java.ext.dirs システム変数で指定されたパスにあるすべてのクラス ライブラリをロードします。
    • アプリケーション クラス ローダー: ユーザー クラス パス (ClassPath) 上のすべてのクラス ライブラリのロードを担当します。これはデフォルトのクラス ローダーです。
    • 親委任モデルでは、最上位の起動クラス ローダーを除くすべてのクラス ローダーが独自の親クラス ローダーを持つ必要があります。
      • 通常、親ローダーのコードを再利用するには、構成関係を使用します。
  • 親委任モデルの作業プロセス
    • クラス ローダーがクラス ロード リクエストを受信した場合、最初にそれ自体でロードしようとするのではなく、リクエストを親クラス ローダーに委任して完了します。これはクラス ローダーの各レベルの場合であるため、すべてのロード リクエストは最後のクラスローダーに送信されます。トップレベルの起動クラスローダーでは、親クラスが(このクラスなしで)ロードできない場合にのみ、サブクラスはそれを単独でロードしようとします。
  • 親委任モデルを使用すると、プログラムのさまざまなクラスローダー環境で同じクラスを保証できます。
  • 親委任モデルを使用しない場合、各クラス ローダーが単独でロードすると非常に混乱し、同じ名前のクラスがロードされない可能性があります。
  • 親委任モデルのソースコードプロセス
    • まず、ロード要求された型が既にロードされているかどうかを確認します。ロードされていない場合は、親ローダーのloadClass() メソッドを呼び出します。親ローダーが空の場合は、デフォルトで起動クラスローダーが親ローダーとして使用されます。親クラスローダーがロードに失敗し、スローされます。例外が発生した後、独自の findClass() メソッドを呼び出してロードを試みます。
  • 親の委任モデルを打破する
    • 初めて、親委任モデルが登場する以前から、クラスローダーや抽象クラスという概念が存在しており、これらのコードと互換性を持たせる必要があり、loadClass()がサブクラスによって上書きされる可能性は避けられませんでした。
    • 2 回目は親委任モデル自体の欠陥が原因で発生し、基本型をユーザーのコードにコールバックする必要があり、問題を解決するためにスレッド コンテキスト クラス ローダーが追加されました。
    • 3 回目は、コードのホット リプレースメント、モジュールのホット デプロイメントなど、ユーザーがプログラム ダイナミクスを追求することによって発生します。
      • OSGI によるモジュラー ホット デプロイメントの実現の鍵は、親委任モデルで推奨されているツリー構造ではなく、カスタム クラス ロード メカニズムの実装です。
      • java.* で始まるクラスを親クラスローダーに委任してロードします。
      • それ以外の場合、デリゲート リスト内のクラスは、ロードするために親クラス ローダーに委任されます。

ここに画像の説明を挿入
ここに画像の説明を挿入

4. Javaモジュラーシステム

  • モジュール性という主要な目標の達成 – 構成可能なパッケージング分離メカニズム
  • Java モジュール定義には以下も含まれます
    • 他のモジュールへの依存関係のリスト
    • エクスポートされたパッケージのリスト
    • パッケージリストを開く
    • 利用サービス一覧
    • サービスを提供する実装のリスト
  • モジュールの互換性
    • JDK9では「クラスパス」(ClassPath)に対応する「モジュールパス」(ModulePath)という概念が提案されました。
  • モジュールの下のクラスローダー
    • 拡張クラス ローダー (Extension Class Loader) はプラットフォーム クラス ローダー (Platform Class Loader) に置き換えられます。3 層クラス ローダーと親委任モデルは変更されていません。

おすすめ

転載: blog.csdn.net/baidu_40468340/article/details/128798209