JVM クラスロードメカニズムについて話す [非専門家]

JVM クラスロードメカニズムについて話す [非専門家]

[[396813]]

クラスローディングメカニズム

クラスは、一度にすべてのクラスをロードするのではなく、実行時に最初に使用されるときに動的にロードされます。一度に読み込むと大量のメモリを消費するからです。

クラスのライフサイクル

以下の 7 つのステージが含まれます。

  • 「読み込み中」
  • 検証
  • 準備
  • 解決
  • 初期化
  • 使用
  • 荷降ろし

クラスロードプロセス --- 新しいオブジェクトを作成するプロセス

読み込み、検証、準備、解析、初期化の 5 つの段階が含まれます。

1. ロード

読み込みプロセスでは、次の 3 つのことが達成されます。

バイナリ バイト ストリームは、次の方法で取得できます。

  • ZIP パッケージから読み取り、JAR、EAR、WAR 形式の基礎となります。
  • インターネットから取得される最も一般的なアプリケーションはアプレットです。
  • 動的プロキシ テクノロジなどの実行時計算生成では、java.lang.reflect.Proxy の ProxyGenerator.generateProxyClass のプロキシ クラスのバイナリ バイト ストリームが使用されます。
  • 他のファイルによって生成されます。たとえば、対応する Class クラスは JSP ファイルによって生成されます。
  • 完全修飾名でクラスを定義するバイナリ バイト ストリームを取得します。
  • バイト ストリームによって表される静的ストレージ構造をメソッド領域のランタイム ストレージ構造に変換します。
  • メソッド領域内のクラスの各種データへのアクセスエントリとして、クラスを表す Class オブジェクトがメモリ内に生成されます。

2. 検証

フォーマット検証: クラス ファイルの仕様に準拠していることを確認します。セマンティック検証: final としてマークされた型にサブクラスが含まれているかどうかを確認します。クラスの最終メソッドがサブクラスによってオーバーライドされているかどうかを確認します。親クラスとサブクラスの間に互換性のないメソッド宣言がないことを確認します (たとえば、メソッド シグネチャは同じですが、メソッドの戻り値が異なるなど)。操作検証: オペランド スタック内のデータが正しく操作され、定数プール内のさまざまなシンボリック参照が検証されます (通常は解析フェーズで実行され、シンボリック参照に記述されている完全修飾名を使用して指定された型を見つけることができるかどうか、クラス メンバー情報のアクセス修飾子がアクセスを許可するかどうかなどをチェックします)。

3. 準備

クラス変数は静的に変更される変数です。準備フェーズでは、クラス変数にメモリを割り当て、メソッド領域のメモリを使用して初期値を設定します。

  1. 公共 静的 整数値 = 123;

クラス変数が定数の場合、0 ではなく、式で定義された値に初期化されます。たとえば、次の定数値は 0 ではなく 123 に初期化されます。

  1. 公共 静的最終int値 = 123;

この段階ではインスタンス変数にメモリは割り当てられません。オブジェクトがインスタンス化されるときに、オブジェクトとともにヒープ内に割り当てられます。

4. 分析

定数プール内のシンボリック参照を直接参照に変換します (メソッドを直接呼び出せるように、メモリ内のクラス、フィールド、またはメソッドのポインターまたはオフセットを取得します)。これは初期化後に実行でき、Java 動的バインディングをサポートできます。

上記の 2、3、4 の 3 つのステージを総称してリンク ステージと呼びます。リンク段階で必要なのは、JVM にロードされたバイナリ バイト ストリームのクラス データ情報を JVM のランタイム状態にマージすることです。

5. 初期化

初期化フェーズは、仮想マシンがクラスコンストラクタを実行するときです。 () メソッドは、クラスで定義された Java プログラム コードを実際に実行し始めます。前の準備段階で、クラス変数にはシステムに必要な初期値が割り当てられています。初期化段階では、クラス変数やその他のリソースは、主にプログラマーがプログラムを通じて作成した主観的な計画に従って初期化されます。

() は、クラス内のクラス変数のすべての代入アクションを自動的に収集し、静的ステートメント ブロック内のステートメントをマージするコンパイラによって生成されます。コンパイラが収集する順序は、ソース ファイル内でのステートメントの出現順序によって決まります。特に注意すべき点は、静的ステートメント ブロックは、それより前に定義されたクラス変数にのみアクセスでき、それより後に定義されたクラス変数には値を割り当てることはできますが、アクセスすることはできないということです。

仮想マシンはクラスを保証する() メソッドは、マルチスレッド環境で正しくロックされ、同期されます。複数のスレッドが同時にクラスを初期化する場合、1つのスレッドのみがクラスを実行します。 () メソッドを実行すると、他のスレッドはブロックされ、アクティブなスレッドが実行されるまで待機します。 () メソッドが完了しました。クラスにいる場合() メソッドにより複数のスレッドがブロックされる可能性があります。実際のプロセスでは、この種のブロックは非常に隠されています。

上記の手順は、簡単に次の 2 つの手順に分けることができます。

  • クラス変数への代入操作
  • 静的コード ブロックを実行します。静的コード ブロックは、JVM によってのみ呼び出すことができます。複数のスレッドが同時にクラスを初期化する必要がある場合、そのクラスに対して初期化操作を実行できるのは 1 つのスレッドのみで、他のスレッドは待機する必要があります。アクティブ スレッドがクラスの初期化操作を完了した後にのみ、他の待機中のスレッドに通知されます。

最後に、メソッド領域には、クラスの静的変数、クラス初期化コード (静的変数を定義する場合の代入文と静的初期化コード ブロック)、インスタンス変数定義、インスタンス初期化コード (インスタンス変数を定義する場合の代入文インスタンス コード ブロックとコンストラクター)、インスタンス メソッド、および親クラスへのクラス情報参照など、現在のクラスのクラス情報が格納されます。

オブジェクトの作成

クラスが初めて使用される場合は、オブジェクトを作成する前に上記のクラス読み込みプロセスを実行する必要があります。

「1.オブジェクトに必要なメモリをヒープ領域に割り当てる

割り当てられたメモリには、このクラスとその親クラスのすべてのインスタンス変数が含まれますが、静的変数は含まれません。

「2. 「すべてのインスタンス変数にデフォルト値を割り当てる」

メソッド領域のインスタンス変数の定義をヒープ領域にコピーし、デフォルト値を割り当てる

「3.インスタンス初期化コードを実行する」

初期化の順序は、最初に親クラスを初期化し、次に子クラスを初期化します。初期化中は、最初にインスタンス コード ブロックが実行され、次にコンストラクターが実行されます。 (クラス内の静的コードが最初に実行され、静的メンバー変数の初期化と静的ステートメント ブロックの実行が含まれます。次に、クラス内の非静的コードが実行され、非静的メンバー変数の初期化と非静的ステートメント ブロックの実行が含まれます。最後にコンストラクターが実行されます。継承の場合は、親クラスの静的コードが最初に実行され、次に子クラスの静的コードが実行され、次に親クラスの非静的コードとコンストラクターが実行され、最後に子クラスの非静的コードとコンストラクターが実行されます)

「4. Child c = new Child() という形式の参照 c がある場合、スタック領域に Child 型の参照変数 c を定義し、それにヒープ オブジェクトのアドレスを割り当てます。」

「各サブクラス オブジェクトは親クラス オブジェクトへの参照を保持する」ことに注意してください。親クラス オブジェクトは super キーワードを介して内部的に呼び出すことができますが、外部からはアクセスできません。

継承がある場合、初期化の順序は次のようになります。

  • 親クラス (静的変数、静的ステートメントブロック)
  • サブクラス (静的変数、静的ステートメントブロック)
  • 親クラス(インスタンス変数、共通ステートメントブロック)
  • 親クラス(コンストラクタ)
  • サブクラス(インスタンス変数、共通ステートメントブロック)
  • サブクラス(コンストラクタ)

クラス初期化ケース

アクティブな引用

仮想マシン仕様では、ロードするタイミングについては制約を課していませんが、クラスを初期化する必要がある状況 (ロード、検証、準備が実行される状況) は次の 5 つだけであると厳密に規定されています。

  • 4 つのバイトコード命令 new、getstatic、putstatic、invokestatic に遭遇したときに、クラスが初期化されていない場合は、まずその初期化をトリガーする必要があります。これら 4 つの命令を生成する最も一般的なシナリオは、new キーワードを使用してオブジェクトをインスタンス化する場合です。クラスの静的フィールドを読み取ったり設定したりする場合 (final によって変更され、コンパイル時に結果が定数プールに配置される静的フィールドを除く)。クラスの静的メソッドを呼び出すときも同様です。
  • java.lang.reflect パッケージのメソッドを使用してクラスをリフレクション的に呼び出す場合、クラスが初期化されていない場合は、まずその初期化をトリガーする必要があります。
  • クラスを初期化するときに、その親クラスが初期化されていないことが判明した場合は、まずその親クラスの初期化をトリガーする必要があります。
  • 仮想マシンが起動すると、ユーザーは実行するメイン クラス (main() メソッドを含むクラス) を指定する必要があり、仮想マシンは最初にこのメイン クラスを初期化します。

受動的な引用

上記の動作は、クラスへのアクティブ参照と呼ばれます。さらに、クラスを参照する他のすべての方法では初期化がトリガーされません。これはパッシブ参照と呼ばれます。受動的な引用の一般的な例は次のとおりです。

  • サブクラスを介して親クラスの静的フィールドを参照しても、サブクラスは初期化されません。
  1. システム。出力.println(SubClass.value); // 値フィールドはスーパークラスで定義されます

配列定義を通じてクラスを参照しても、クラスの初期化はトリガーされません。このプロセスは、配列クラスを初期化します。配列クラスは、仮想マシンによって自動的に生成され、配列のプロパティとメソッドを含む Object から直接継承されたサブクラスです。

  1. スーパークラス[] sca = 新しいスーパークラス[10];
  • 定数は、コンパイル フェーズ中に呼び出し元のクラスの定数プールに格納されます。本質的には、定数を定義するクラスを直接参照しないため、定数を定義するクラスの初期化はトリガーされません。
  1. システム。 .println(ConstClass.HELLOWORLD);を出力します

クラスとクラスローダー

2 つのクラスが等しくなるためには、クラス自体が等しく、同じクラス ローダーを使用してロードされている必要があります。これは、各クラスローダーに個別のクラス名前空間があるためです。最終的な等価性には、Class オブジェクトの equals() メソッド、isAssignableFrom() メソッド、isInstance() メソッドからの true の戻り結果と、instanceof キーワードを使用してオブジェクトの所有権関係を判別した結果の true 結果が含まれます。

Java 仮想マシンの観点から見ると、クラス ローダーは次の 2 つだけです。

  • C++ で実装された Bootstrap ClassLoader は仮想マシン自体の一部です。
  • 他のすべてのクラス ローダーは Java で実装され、仮想マシンから独立しており、抽象クラス java.lang.ClassLoader から継承されます。

そして、上記は、BootstrapClassLoader、ExtensionClassLoader、App ClassLoaderの3種類のローダーに分けられます。

  • BootstrapClassLoader は、JVM カーネルに組み込まれたローダーです。これは C++ で記述されており、主に JAVA_HOME/lib の下または -Xbootclasspath パラメータで指定されたパスにあるクラス ライブラリを読み込みます。仮想マシンによって認識されます (rt.jar などのファイル名のみ。名前が一致しないクラス ライブラリは、lib ディレクトリに配置されていても読み込まれません)。

起動クラスローダーは、Java プログラムによって直接参照することはできません。ユーザーがカスタム クラス ローダーを作成するときに、読み込み要求を起動クラス ローダーに委任する必要がある場合は、代わりに null を使用できます。

  • 拡張クラス ローダー (ExtensionClassLoader) は JAVA で記述され、ExtClassLoader (sun.misc.Launcher$ExtClassLoader) によって実装されます。それは、 /lib/ext または java.ext.dir システム変数で指定されたパス内のすべてのクラス ライブラリがメモリにロードされます。開発者は拡張クラスローダーを直接使用できます。

親クラスローダーは Bootstrap です。

  • アプリケーション クラス ローダー このクラス ローダーは、AppClassLoader (sun.misc.Launcher$AppClassLoader) によって実装されます。通常、アプリケーションのクラスパス ディレクトリ内のすべての jar ファイルとクラス ファイルをロードする役割を担います。

開発者はこのクラスローダーを直接使用できます。アプリケーションが独自のクラス ローダーをカスタマイズしていない場合、これは通常、プログラム内のデフォルトのクラス ローダーになります。

親ローダーは ExteClassLoader です。

親委任モデル

アプリケーションは、クラスのロードを実現するために連携して動作する 3 つのクラス ローダーによってロードされます。さらに、独自のクラスローダーを追加することもできます。次の図は、親委任モデルと呼ばれるクラス ローダー間の階層関係を示しています。ここでの親子関係は、一般的に継承ではなく委任を通じて実現されます。

  • ワークフロー

クラス ローダーがクラスのロード要求を受信した場合、クラス自体をロードしようとはせず、要求を親クラス ローダーに渡して完了させます。これはクラスローダーのすべてのレベルに当てはまります。したがって、すべてのクラス ロード要求は、最上位の起動クラス ローダーに渡す必要があります。親クラス ローダーがロード要求を完了できない (クラスが検索範囲内に見つからない) と報告した場合にのみ、子クラス ローダーはクラス自体のロードを試行します。

  • 利点

これにより、Java クラスとそのクラス ローダーは優先順位を持つ階層関係を持つことができ、基本クラスが統一されます。

たとえば、java.lang.Object は rt.jar に保存されます。別の java.lang.Object を記述して ClassPath に配置すると、プログラムをコンパイルできます。親委任モデルが存在するため、rt.jar 内のオブジェクトは ClassPath 内のオブジェクトよりも優先順位が高くなります。これは、rt.jar 内のオブジェクトがスタートアップ クラス ローダーを使用するのに対し、ClassPath 内のオブジェクトがアプリケーション クラス ローダーを使用するためです。 rt.jar 内のオブジェクトの方が優先度が高いため、プログラム内のすべてのオブジェクトはこのオブジェクトになります。

  • デモ

以下は、抽象クラス java.lang.ClassLoader のコード スニペットです。このコード スニペットでは、loadClass() メソッドが次のように実行されます。まず、クラスがロードされているかどうかを確認し、ロードされていない場合は、親クラス ローダーでクラスをロードします。親クラス ローダーのロードに失敗した場合は、ClassNotFoundException がスローされ、自分でロードを試みます。

  1. パブリック抽象クラス ClassLoader {
  2. //委任用の親クラスローダー
  3. プライベート最終 ClassLoader 親;
  4.  
  5. パブリックClass<?> loadClass(String name ) は ClassNotFoundException をスローします {
  6. loadClass( name , false )を返します
  7. }
  8.  
  9. 保護された Class<?> loadClass(String name , boolean resolve) は ClassNotFoundException をスローします {
  10. 同期化 (getClassLoadingLock(名前)) {
  11. //まずクラスがすでにロードされているかどうかを確認します
  12. クラス<?> c = findLoadedClass(名前);
  13. c == nullの場合{
  14. 試す {
  15. 親がnull場合
  16. c = parent.loadClass(名前 false );
  17. }それ以外{
  18. c = findBootstrapClassOrNull(名前);
  19. }
  20. } キャッチ (ClassNotFoundException e) {
  21. // クラスが見つからない場合は ClassNotFoundException がスローされます
  22. // nullでない親クラスローダーから
  23. }
  24.  
  25. c == nullの場合{
  26. //それでも見つからない場合は findClassを呼び出します 注文 
  27. //クラスを見つけます
  28. c = findClass(名前);
  29. }
  30. }
  31. if (解決) {
  32. クラスを解決します(c);
  33. }
  34. cを返します
  35. }
  36. }
  37.  
  38. 保護された Class<?> findClass(String name ) は ClassNotFoundException をスローします {
  39. 新しい ClassNotFoundException(名前) をスローします。
  40. }
  41. }
  • カスタムクラスローダーの実装

次のコードの FileSystemClassLoader は、java.lang.ClassLoader から継承され、ファイル システムにクラスをロードするために使用されるカスタム クラス ローダーです。まず、クラスの完全な名前に従ってファイルシステム上のクラスのバイトコードファイル (.class ファイル) を検索し、次にファイルの内容を読み取り、最後に defineClass() メソッドを通じてバイトコードを java.lang.Class クラスのインスタンスに変換します。

java.lang.ClassLoader の loadClass() メソッドは、親委任モデルのロジックを実装します。カスタム クラス ローダーは通常これをオーバーライドしませんが、findClass() メソッドをオーバーライドする必要があります。

  1. パブリッククラスFileSystemClassLoaderはClassLoaderを拡張します{
  2.  
  3. プライベート文字列 rootDir;
  4.  
  5. パブリックFileSystemClassLoader(文字列rootDir) {
  6. this.rootDir = rootDir;
  7. }
  8.  
  9. 保護された Class<?> findClass(String name ) は ClassNotFoundException をスローします {
  10. byte[] classData = getClassData(名前);
  11. クラスデータ == null場合
  12. 新しい ClassNotFoundException() をスローします。
  13. }それ以外{
  14. defineClass( name , classData, 0, classData.length )を返します
  15. }
  16. }
  17.  
  18. プライベートbyte[] getClassData(String className) {
  19. 文字列パス = classNameToPath(className);
  20. 試す {
  21. 入力ストリーム ins = 新しい FileInputStream(path);
  22. ByteArrayOutputStream baos = 新しい ByteArrayOutputStream();
  23. バッファサイズ = 4096;
  24. byte[] buffer = 新しいbyte[bufferSize];
  25. 読み取りバイト数int ;
  26. while ((bytesNumRead = ins. read (buffer)) != -1) {
  27. baos.write(バッファ、0、読み取りバイト数);
  28. }
  29. baos.toByteArray()を返します
  30. } キャッチ (IOException e) {
  31. e.printStackTrace();
  32. }
  33. 戻る ヌル;
  34. }
  35.  
  36. プライベート文字列クラス名ToPath(文字列クラス名) {
  37. rootDir + File.separatorCharを返す
  38. + クラス名。 replace ( '.' , File.separatorChar) + ".class" ;
  39. }
  40. }

巨人の肩

プログラマーの春の採用ノートからの抜粋

https://github.com/CyC2018/CS-Notes

この記事はWeChatの公開アカウント「Multi-Select Parameters」から転載したものです。以下のQRコードからフォローできます。この記事を転載する場合は、Multi-Select Parameters の公開アカウントにご連絡ください。

<<:  SaaS をサービスの観点から見ると何がわかるでしょうか?

>>:  クラウドのために生まれた Alibaba Cloud がクラウド サーバー オペレーティング システム Alinux3 をリリース: パフォーマンスが最大 40% 向上

推薦する

スタートアップは SEO を活用して Baidu ブランドゾーンをどのように作成できるでしょうか?

企業にとってブランドが重要であることは自明です。ブランド構築に費やされるマーケティング費用は、同社の...

コンテナが攻撃されたときの対処方法: インシデント対応計画

翻訳者 |趙青棠校正:孫淑娟コンテナは、コード、構成ファイル、ライブラリ、システム ツールなど、あら...

Baidu クイックコレクション体験の概要

ウェブページをランキングに載せたい場合、Baidu にインデックスされることが最初のステップです。B...

テンセントクラウドがKPLイベントに協力、ワンストップリモート制作を提供

2020年グローバルeスポーツリーダーサミットとテンセントeスポーツ年次会議において、テンセントクラ...

ステーション B で紛争がなくなるのはいつでしょうか?

2月25日、ビリビリは今年度の財務報告書を発表した。年末が近づくにつれ、ビリビリのユーザー数の増加も...

cloudcone: 最新のストレージ VPS、超大容量ハードディスク VPS、年間 20 ドル、最小構成 - 1G メモリ/1 コア/250g/5T トラフィック

Cloudcone は、非常に安価なストレージ VPS の正式リリースを発表しました。これほど安価な...

新しいウェブサイトは、4つのことを行うことですぐにその重量を改善します

新しく立ち上げたウェブサイトでは、サイトの重量をすぐに増やしたいというのが、すべてのウェブマスターに...

UAE VPS クラウド サーバー: estnoc、UAE データ センター、1Gbps 帯域幅、月額 10 ユーロから

UAE VPS と UAE クラウド サーバー、つまりアラブ首長国連邦の VPS とクラウド サーバ...

APPがユーザーを呼び戻すための4つのチャネル:EDM、プッシュ、SMS、パブリックアカウント

この記事の著者は、EDM 電子メール プッシュ、SMS 通知、 APP メッセージ プッシュ、パブリ...

今日の医療サイト SEO における 3 つのよくある混乱の解釈

6月と7月に起きた百度のKステーション事件をまだ覚えているウェブマスターは多いと思いますが、特に医療...

インターネットコミュニティの進化特性:トピックセグメンテーショングループが小さくなり、情報価値が上昇

私が初めてコミュニティに触れたのは、中学生の時、クラスメイトが21.cnというウェブサイトを勧めてく...

集中砲火を浴びせる動画サイトは、Youku や iQiyi の進化の可能性となるでしょうか?

要約: 連射動画の焦点は動画ではなく連射であり、連射は実は社会や文化と関係があります。この2つが動画...

2022年のクラウドコンピューティング開発のトレンド

感染症の流行から国際的なサプライチェーンの問題まで、近年、世界のビジネス環境は大きく変化しており、私...

ウェブサイト最適化のあらゆる詳細とすべてのステップを確認する

ウェブサイトの最適化は長いプロセスです。初期の計画からウェブサイトの構築、サイト内最適化、外部リンク...