この記事はWeChatの公開アカウント「Mu Xiaoma Nong」から転載したもので、著者はMu Xiaonongです。この記事を転載する場合は、穆小馬農の公式アカウントまでご連絡ください。 クラスの読み込み Java 仮想マシンのクラス読み込みプロセスは、クラス ファイルをメモリに読み込み、クラス ファイル内のデータを検証、変換、解析、初期化し、最終的に仮想マシンで直接使用できる Java 型を形成することです。 コンパイル時に接続作業が必要な言語とは異なり、Java では、型の読み込み、接続、初期化がすべてプログラム実行中に完了します。この戦略により、クラスをロードする際のパフォーマンスのオーバーヘッドがわずかに増加しますが、Java アプリケーションの柔軟性が向上します。 クラスを使用するときに、クラスがディスクからメモリにロードされていない場合、JVM は 3 段階の戦略 (ロード、接続、初期化) を通じてクラスを初期化します。これら 3 つのステップを完了する JVM の名前は、クラス ロードまたはクラス初期化と呼ばれます。 クラスの読み込みタイミング Java 仮想マシン仕様では、クラス ロードの最初の段階であるロードをいつ開始するかについて、必須の制約は課されていません。代わりに、仮想マシンの特定の実装によって決定されます。ただし、初期化段階については、仮想マシン仕様では、クラスを直ちに初期化する必要がある状況は「5 つだけ」であると厳密に規定されています (当然、その前に読み込み、検証、準備を開始する必要があります)。具体的な状況は以下のとおりです。 クラス ファイルをロードするタイミング:
シーケンス番号1の詳細な説明:
注: newarray 命令は配列型自体の初期化のみをトリガーし、関連する型の初期化はトリガーしません。たとえば、newString[] は String[] クラスの初期化を直接トリガーするだけです。つまり、クラス [Ljava.lang.String の初期化をトリガーしますが、String クラスの初期化を直接トリガーすることはありません。 これら 4 つの命令を生成する最も一般的な Java コード シナリオは次のとおりです。 クラスの初期化をトリガーするこれらの 5 つのシナリオでは、仮想マシン仕様では非常に強力な修飾子「if and only if」が使用され、これらの 5 つのシナリオでの動作はクラスへのアクティブ参照と呼ばれます。さらに、クラスを参照する他のすべての方法では初期化がトリガーされません。これはパッシブ参照と呼ばれます。 クラスのインスタンス化とクラスの初期化は完全に異なる概念であることに注意してください。
受動的な引用の 3 つのシナリオ: サブクラスを介して親クラスの静的フィールドを参照しても、サブクラスは初期化されません。
出力:
静的フィールドの場合、このフィールドを直接定義するクラスのみが初期化されます。したがって、親クラスで定義された静的フィールドをサブクラスを通じて参照すると、サブクラスではなく親クラスの初期化のみがトリガーされます。サブクラスのロードと検証をトリガーするかどうかについては、仮想マシン内で明確に指定されておらず、仮想マシンの具体的な実装に依存します。 Sun HotSpot 仮想マシンの場合、-XX:+TraceClassLoading パラメータを使用して、この操作によってサブクラスがロードされることを確認できます。 上記の場合、count フィールドは Father クラスで定義されているため、クラスが初期化されます。さらに、Father クラスを初期化するときに、仮想マシンはその親クラス Test1 が初期化されていないことを検出するため、仮想マシンは最初に親クラス Test1 を初期化し、次にサブクラス Father を初期化しますが、Son は初期化されません。 配列定義を通じてクラスを参照しても、このクラスの初期化はトリガーされません。
実行後、「Init M!!!」という出力がないことがわかります。これは、クラスの初期化フェーズがトリガーされていないことを示しています。 定数は、コンパイル フェーズ中に呼び出し元のクラスの定数プールに格納されます。本質的には、定数を定義するクラスを直接参照しないため、定数を定義するクラスの初期化はトリガーされません。
上記のコードを実行すると、InitConstClass の出力はありません。これは、ConstClass クラスの定数 COUNT が Java ソース コードで参照されているにもかかわらず、コンパイル フェーズでの定数伝播の最適化によって、定数値 "1" が Test3 定数プールに格納されているためです。定数 ConstClass.COUNT への参照は、実際には Test3 クラス独自の定数プールへの参照に変換されます。つまり、Test3 クラス ファイルには ConstClass クラスのシンボリック参照エントリが実際には存在せず、クラス ファイルにコンパイルされた後、2 つのクラスには関係がありません。 クラスのロードプロセス Class というファイルがあり、ハードディスク上に静かに存在し、快適な生活を楽しんでいます。ハードディスクからメモリに到達するには、どのようなプロセスを経る必要がありますか?クラスがメモリに入るには 3 つのステップがあります。
1. ロード ロードはクラス ロード プロセスの段階です。ロードとは、現在のクラスのクラス ファイルをメモリに読み込み、java.lang.Class のオブジェクトを作成することを指します。つまり、プログラム内で任意のクラスが使用されると、システムは java.lang.Class オブジェクトを作成します。 読み込みフェーズでは、仮想マシンは次の 3 つの処理を完了する必要があります。 完全修飾名を使用してクラスを定義するバイナリ バイト ストリームを取得します (クラス ファイルからのみ取得できるとは指定されておらず、ネットワーク、動的生成、データベースなどの他のチャネルからも取得できます)。 このバイトストリームによって表される静的ストレージ構造をメソッド領域のランタイムデータ構造に変換します。 メソッド領域内のこのクラスのさまざまなデータへのアクセスポイントとして、このクラスを表す java.lang.Class オブジェクトをメモリ内に生成します。 通常、クラス ローダーは、クラスをロードするために、クラスが「最初に使用される」まで待つ必要はありません。 Java 仮想マシン仕様では、システムが特定のクラスをプリロードできます。ロードフェーズと接続フェーズの内容の一部は交互に実行されます。読み込みフェーズはまだ完了しておらず、接続フェーズが開始している可能性がありますが、フェーズ間で実行されるアクションは依然として接続フェーズの内容に属し、これら 2 つのフェーズの開始時間は依然として固定された順序を維持します。 2. 接続する クラスがロードされると、システムは対応するクラス オブジェクトを生成し、リンク フェーズに入ります。このフェーズでは、クラスのバイナリ データを JRE にマージします。リンクフェーズは 3 つのサブフェーズに分かれています。 1.1 検証 検証は接続フェーズの最初のステップです。このフェーズの主な目的は、クラス ファイルのバイト ストリームに含まれる情報が現在の仮想マシンの要件を満たし、仮想マシン自体のセキュリティを危険にさらさないことを確認することです。 Java は C/C++ に比べて比較的安全な言語です。検証フェーズは非常に重要です。このフェーズが厳密であるかどうかによって、Java 仮想マシンが悪意のあるコードからの攻撃に耐えられるかどうかが決まります。検証入力のバイト ストリームがクラス ファイル形式の制約に準拠していない場合、仮想マシンは java.lang.VerifyError 例外またはサブクラス例外をスローします。一般的に、検証は、ファイル形式の検証、メタデータの検証、バイトコードの検証、シンボル参照の検証という 4 つの検証アクションに分けられます。 ファイル形式の検証: 主に、バイト ストリームがクラス ファイル形式の仕様に準拠し、現在のバージョンの仮想マシンで処理できるかどうかを検証します。主に以下の側面が含まれます。
メタデータ検証: 主にバイトコードで記述された情報の意味解析を実行します。主な目的は、クラスのメタデータに対してセマンティック検証を実行し、Java 言語の構文仕様に準拠しているかどうかを分析し、Java 言語仕様に準拠していないメタデータ情報がないことを確認することです。この段階の主な検証の側面は次のとおりです。
バイトコード検証: データフローと制御フローを通じてプログラムのセマンティクスが合法かつ論理的であるかどうかを分析する、最も重要かつ複雑な検証手順です。主にクラスのメソッド本体の検証分析を実行し、検証されたクラスが動作中に仮想マシンのセキュリティを危険にさらさないことを確認します。 オペランドスタックのデータ型と命令コードシーケンスがいつでも連携できることを保証する(例えば、オペランドスタックにint型のデータがある場合、それが使用されるときにlong型としてローカル変数テーブルにロードされないことが保証される) ジャンプ命令は、メソッド本体の外側のバイトコード命令には追加されません。 メソッド本体のデータ変換が有効であることを確認します。たとえば、サブクラス オブジェクトを親クラスのデータ型に割り当てることはできますが、親クラスをサブクラスのデータ型に割り当てることはできません。 シンボル参照の検証: シンボル参照を直接参照に変換する場合、この変換は第 3 フェーズ (バイトコード検証) の解析フェーズ中に行われます。主な目的は、参照がアクセスされ、アクセスできないクラスの問題が発生しないようにすることです。 1.2 準備 クラス変数にメモリを割り当て、クラス変数の初期値を設定する段階。これらの変数によって使用されるメモリはメソッド領域に割り当てられます。準備段階では、クラス ファイルの静的変数にデフォルト値が割り当てられます。注意: 初期値ではありません。たとえば、public static ini = 8 の場合、このステップでは i は 8 に割り当てられるのではなく、最初に 0 に割り当てられます。 基本タイプのデフォルト値:
通常、初期値は 0 ですが、上記の定数に final クラス修飾子を追加すると、初期値は指定した値になります。コンパイル時に、Javac は i の初期値を 8 に変更します。 1.3 分析 クラス ファイル定数プールで使用されるシンボリック参照を直接メモリ アドレスに変換し、その内容に直接アクセスします。シンボリック参照: 参照先ターゲットを説明するためにシンボルのセットを使用します。シンボルはリテラル形式の任意のリテラルにすることができます。競合がなく、位置を特定できる限り、直接参照できます。ターゲットを直接指すポインター、相対オフセット、または間接的にターゲットを特定できるハンドルにすることができます。直接参照がある場合、参照先ターゲットはメモリ内にすでに存在している必要があります。 3. 初期化 初期化とは、クラスの静的変数に正しい初期値を割り当てることです。先ほど、準備フェーズではデフォルト値をコピーし、初期化フェーズでは初期値を静的変数に割り当てることを説明しました。次の文を見てください。
まず、バイトコード ファイルがメモリにロードされた後、接続検証が最初に実行されます。準備段階を経て、i にメモリが割り当てられます。静的であるため、この時点でのiのデフォルトの初期値はint型の0に等しいため、iは0になります。初期化になると、iには実際には8の値が割り当てられます。 クラスローダー クラス ローダーは、すべてのクラスをロードし、メモリにロードされたクラスの java.lang.Class インスタンス オブジェクトを生成する役割を担います。クラスが JVM にロードされると、同じクラスは再度ロードされません。オブジェクトに一意の識別子があるのと同様に、JVM にロードされるクラスにも一意の識別子があります。 JVM 自体にはクラスローダー階層があります。このクラスローダー自体は通常のクラスです。すべてのクラスはクラス ローダーによってメモリにロードされます。これを ClassLoader、トップレベルの親クラス、抽象クラスと呼ぶこともできます。 ブートストラップ: クラス ローダーの読み込みプロセスは、読み込みのためにさまざまなレベルに分割されます。異なるクラスローダーは異なるクラスをロードします。トップレベルの Bootstrap として、rt.jar、charset.jar、その他のコア クラスなど、JDK のコア コンテンツを lib にロードします。このローダーを取得するために getClassLoader() を呼び出し、結果が Null の場合、トップレベルのローダーに到達したことを意味します。 拡張機能: 拡張機能ローダー拡張クラス。拡張機能パッケージ内のさまざまなファイルをロードします。これらの拡張機能パッケージは、JDK インストール ディレクトリ jre/lib/ext の下の jar にあります。 アプリ: これは通常使用するアプリケーションで、クラスパスで指定されたコンテンツを読み込むために使用されます。 カスタム ClassLoader: カスタム ClassLoader は、独自のカスタム ローダーをロードします。 Custom ClassLoader の親クラスローダーはアプリケーションです。 Extension の親クラス ローダーは Bootstrap です。 注: これらは継承関係ではなく、委任関係です。
クラスローダーの継承 この図は、ClassLoader が文法的に誰を継承するかを示しています。この図は継承関係ではなく、単純な文法関係を示しています。上記のクラスの読み込みとは何の関係もないことを思い出してください。実際にはこの図は無視できます。 親の委任 親ローダー: 親ローダーは、「クラスローダーのローダー」でも「クラスローダーの親クラスローダー」でもありません。親委任は、子から親へ、そして親から子への親委任プロセスです。 クラス ローダーは、クラスのロード要求を受信すると、まずカスタムのクラスからそれを探します。同時に、内部的にキャッシュも維持します。キャッシュ内に見つかった場合は、結果が直接返されます。見つからない場合は、親クラスに委任し、親クラスは最上位の親クラスまでキャッシュ内を検索します。この時点でキャッシュから必要な結果が得られない場合、父親は「私はあなたのためにこれを行うことはできません。自分で行う必要があります」と言います。その後、息子は対応するクラスを照会し、自分でロードします。末っ子がまだ対応するクラスを見つけられない場合は、例外 Class Not Found Exception がスローされます。 なぜ親の委任なのか? これはクラスローダーにとって必ず聞かれる面接の質問です。 主に安全のため、任意のクラスをメモリにロードできる場合、java.lang.String を記述して危険なコードを記述すると、セキュリティ上の問題が発生するでしょうか?また、Java コア API で定義された型が勝手に置き換えられないようにし、API ライブラリが勝手に変更されるのを防ぐことができます。 2つ目は効率の問題です。キャッシュがある場合は、キャッシュから直接取得します。親クラスまたは子クラスを何度も走査してクエリする必要はありません。 |
>>: エッジコンピューティング クラウドネイティブ オープンソース ソリューションの比較
多くの人のウェブサイトのコレクションが突然減少し、スナップショットが後退しました。これは、ウェブサイ...
みなさんこんにちは。梁磊です。百度ウェブマスタープラットフォームで「手を携えてインターネットエコシス...
1. キングバスの紹介1.1 Kingbusとは何ですか? kingbus は、raft の強力なコ...
オンラインでの収入というと、多くのウェブマスターがタオバオアフィリエイト、SEO最適化、注文受付など...
タイトルを見ると、次のような疑問を抱かずにはいられない人もいるだろう。「Baidu Green Ra...
絶対的に正しいとか、絶対的に間違っているとかいうものは存在せず、この言葉は決して時代遅れになることは...
cheapwindowsvpsは、数日前のメールで、特別版のベアメタル専用サーバーホスティング(サー...
SEO には多くのことが関係しますが、始めるのは非常に簡単です。インターネットとコンピューターの操作...
ビジネスの世界は戦場のようなもので、ウェブサイトの構築は戦争を戦うようなものです。ウェブサイトの構築...
少し前にA5で「ビッグデータ環境における起業のチャンス」という記事を見たのを覚えています。私は主流の...
ウェブサイトを作成することは難しくありませんが、ウェブページやビジネスに適したドメイン名を見つけるの...
ジョン・ログネルド更新日: 2007 年 5 月 11 日午後 12:00 (東部標準時)翻訳:方林...
みなさん、こんにちは。私はハルビン仮想現実ウェブサイト設計です。最近、百度のキーワードランキングが1...
Google がメールデータを検索結果に統合するテストを実施 (写真提供: Tencent Tech...
多くの SEO 担当者は、検索エンジンのダイナミクス、SEO テクニックの学習、ウェブサイトの最適化...