インタビューで尋ねなければならないJVMクラスのロードメカニズムを理解していますか?

序文

今回は、JVMのもう1つの重要な部分である、クラスのロードメカニズムが、意味のないものではなく、直接開かれています。

文章

1.クラスの読み込みのプロセス。

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

1)ロード

「クラスのロード」プロセスの段階。ロード段階では、仮想マシンは次の3つのことを行う必要があります。

  • このクラスを完全修飾名で定義するバイナリバイトストリームを取得します。
  • このバイトストリームで表される静的ストレージ構造をメソッド領域のランタイムデータ構造に変換します。
  • このクラスを表すjava.lang.Classオブジェクトは、メソッド領域内のこのクラスのさまざまなデータへのアクセスエントリとしてメモリに生成されます。

2)確認する

接続フェーズの最初のステップであるこのフェーズの目的は、クラスファイルのバイトストリームに含まれる情報が現在の仮想マシンの要件を満たし、仮想マシン自体のセキュリティを危険にさらさないようにすることです。全体として、検証段階では通常、ファイル形式の検証、メタデータの検証、バイトコードの検証、およびシンボル参照の検証の4つの検証アクションの段階が完了します。

3)準備する

この段階は、クラス変数(静的に変更された変数)にメモリを正式に割り当て、クラス変数の初期値を設定する段階です。これらの変数によって使用されるメモリは、メソッド領域に割り当てられます。ここで説明する初期値は、「通常」のデータ型のゼロ値です。次の表に、Javaのすべての基本データ型のゼロ値を示します。

4)解析

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

5)初期化

初期化段階では、クラスで定義されたJavaプログラムコードが実際に実行されます。準備段階では、変数にシステムが必要とする初期ゼロ値が割り当てられ、初期化段階では、プログラムを通じてプログラマーが作成した主観的な計画に従って、クラス変数およびその他のリソースが初期化されます。

別のより直接的な形式で表現することもできます。初期化フェーズは、クラスコンストラクターの<clinit>()メソッドを実行するプロセスです。<clinit>()は、プログラマーがJavaコードで直接記述するメソッドではありませんが、Javacコンパイラーによって自動的に生成されます。

<clinit>()メソッドは、コンパイラによって生成され、クラス内のすべてのクラス変数の割り当てを自動的に収集し、静的ステートメントブロック(static {}ブロック)内のステートメントをマージします。コンパイラ収集の順序は、次のステートメントによって決定されます。ソースファイル。出現順序に応じて、静的ステートメントブロックの前に定義された変数のみが静的ステートメントブロックでアクセスでき、その後に定義された変数は、前の静的ステートメントブロックで値を割り当てることができますが、できませんアクセスされます。

また、前に初期化についてのインタビューの質問を書きました。興味深い「初期化」のインタビューの質問です。興味のある学生は見てみることができます。 

2. Java仮想マシンにはどのクラスローダーがありますか?

Java仮想マシンの観点からは、2つの異なるクラスローダーのみがあります。

1つはBootstrapClassLoaderで、これはC ++で実装され、仮想マシン自体の一部です。

もう1つは、他のすべてのクラスローダーです。これらはJava言語によって実装され、仮想マシンの外部から独立しており、すべて抽象クラスjava.lang.ClassLoaderから継承されます。

Java開発者の観点から、ほとんどのJavaプログラムは、次の3つのシステムによって提供されるクラスローダーを使用します。

1)クラスローダー(ブートストラップClassLoader)を起動します。

このクラスローダーは、<JAVA_HOME> \ libディレクトリ、または-Xbootclasspathパラメーターで指定されたパスに格納され、仮想マシンによって認識される(rt.jarなどのファイル名でのみ認識される)ファイルを格納する役割を果たします。名前が一致しませんlibディレクトリに配置されていてもクラスライブラリがロードされません)クラスライブラリが仮想マシンメモリにロードされます。

2)拡張クラスローダー(拡張クラスローダー):

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

3)アプリケーションクラスローダー(アプリケーションクラスローダー):

このクラスローダーは、sun.misc.Launcher$AppClassLoaderによって実装されます。このクラスローダーはClassLoaderのgetSystemClassLoader()メソッドの戻り値であるため、一般にシステムクラスローダーと呼ばれます。ユーザーのクラスパス(ClassPath)で指定されたクラスライブラリをロードする役割を果たします。開発者はこのクラスローダーを直接使用できます。アプリケーションが独自のクラスローダーをカスタマイズしていない場合、通常、これはプログラムのデフォルトクラスです。ローダー。

私たちのアプリケーションは、これら3つのクラスローダーによってロードされます。必要に応じて、独自に定義したクラスローダーを追加できます。これらのクラスローダー間の関係は、一般的に示されているとおりです。

3.親の委任モデルとは何ですか?

クラスローダーがクラスロードリクエストを受信した場合、最初にクラス自体をロードしようとはしませんが、リクエストを親クラスローダーに委任して完了させます。これはすべてのレベルのクラスローダーに当てはまるため、すべてのロードリクエストは最終的には最上位のスタートアップクラスローダーに渡され、親ローダーがロード要求を完了できないことを報告した場合(必要なクラスが検索スコープに見つからない場合)にのみ、子ローダーは自分自身をロードしようとします。

クラスロードのソースコードは次のとおりです。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、检查请求的类是否已经被加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 2、将类加载请求先委托给父类加载器
                    if (parent != null) {
                        // 父类加载器不为空时,委托给父类加载进行加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 父类加载器为空,则代表当前是Bootstrap,从Bootstrap中加载类
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException
                    // 说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    // 3、在父类加载器无法加载的时候,再调用本身的findClass方法来进行类加载
                    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;
        }
    }

4.なぜ親の委任モデルを使用するのですか?

1)親委任モデルを使用してクラスローダー間の関係を整理すると、Javaクラスがクラスローダーとともに優先順位階層を持つという明らかな利点があります。

2)親委任モデルが使用されておらず、各クラスローダーがそれを単独でロードする場合、ユーザーがjava.lang.Objectクラスを作成し、それをプログラムのClassPathに配置すると、システムに複数の違いが生じます。オブジェクトクラス、Javaタイプシステムでの最も基本的な動作は保証されておらず、アプリケーションは混乱します。

5.どのシナリオが親の委任モデルを破りますか?

 最も一般的なシナリオは次のとおりです。

1)スレッドコンテキストクラスローダー、通常:JDBCはスレッドコンテキストクラスローダーを使用してドライバー実装クラスをロードします

2)TomcatのマルチWebアプリケーション

3)OSGIはモジュラーホットデプロイメントを実現します

6.なぜ親の委任モデルを破るのですか?

理由は実は非常に単純です。つまり、親委任モデルを使用してもニーズを満たすことができないため、破棄することしかできません。ここでは、インタビューでよく聞かれるTomcatを例として取り上げます。

Tomcatコンテナは同時に複数のWebアプリケーションをデプロイできることを知っています。また、複数のWebアプリケーションが同じjarパッケージに依存しているが、バージョンが異なる場合は簡単です。たとえば、アプリケーション1とアプリケーション2はどちらもSpringに依存し、アプリケーション1はバージョン3.2。*を使用し、アプリケーション2はバージョン4.3。*を使用します。

親の委任モデルに従う場合、現時点ではどのバージョンが使用されていますか?

実際、どのバージョンを使用できないかは、互換性の問題が発生しやすくなります。したがって、Tomcatは親の委任モデルを破ることしか選択できません。

7.親委任モデルを破る方法は?

親の委任モデルを破棄するという考え方も同様です。これは、インタビューでよく聞かれるTomcatの例です。

実際、原則は非常に単純です。上記のクラスロードメソッドソースコード(loadClass)のメソッド修飾子が保護されていることがわかります。したがって、親委任モデルを破棄するには、次の手順のみが必要です。

1)ClassLoaderを継承します。TomcatのWebappClassLoaderは、ClassLoaderのサブクラスURLClassLoaderを継承します。

2)loadClassメソッドを書き直して、独自のロジックを実装します。毎回ロードを親クラスに委任しないでください。たとえば、最初にローカルでロードすると、親委任モデルが破棄されます。

8. Tomcatのクラスローダー?

Tomcatのクラスローダーを以下に示します。

1)ブートストラップクラスローダー:上の図では、拡張クラスローダーが欠落していることがわかります。Tomcatでは、拡張クラスローダーがブートストラップクラスローダーに統合されています。

2)システムクラスローダーはアプリケーションクラスローダーです。Tomcatのシステムクラスローダーは、CLASSPATH環境変数の内容をロードしませんが、次のリソースライブラリからシステムクラスローダーを構築します。

  • $ CATALINA_HOME / bin / bootstrap.jarには、Tomcatサーバーの初期化に使用されるmain()メソッドと、それが依存するクラスローダー実装クラスが含まれています。
  • $ CATALINA_BASE / bin/tomcat-juli.jarまたは$CATALINA_HOME/ bin / tomcat-juli.jar、ロギング実装クラス。
  • tomcat-juli.jarが$CATALINA_BASE/ binに存在する場合は、$ CATALINA_HOME/binにあるものの代わりに使用します。
  • $ CATALINA_HOME / bin / commons-daemon.jar

3)共通ClassLoader:名前からわかるように、Tomcat内部クラスとすべてのWebアプリケーションに表示されるいくつかの共通クラスが主に含まれています。

クラスローダーが検索する場所は、$ CATALINA_BASE / conf / catalina.propertiesのcommon.loaderプロパティによって定義されます。デフォルト設定では、次の場所が順番に検索されます。

  • $ CATALINA_BASE/lib内のパッケージ化されていないクラスとリソース
  • $ CATALINA_BASE/libディレクトリ内のJARファイル
  • $ CATALINA_HOME/libにあるパッケージ化されていないクラスとリソース
  • $ CATALINA_HOME/libディレクトリ内のJARファイル

4)WebappX ClassLoader:Tomcatは、デプロイされたWebアプリケーションごとに個別のクラスローダーを作成します。これにより、異なるアプリケーション間の分離が保証され、クラスとリソースは他のWebアプリケーションからは見えなくなります。ロードされたパスは次のとおりです。

  • Webアプリケーションの/WEB-INF/classesディレクトリにあるパッケージ化されていないすべてのクラスとリソース
  • Webアプリケーションの/WEB-INF/libディレクトリにあるJARファイルのクラスとリソース

9. Tomcatのクラスロードプロセスとは何ですか?

Tomcatのクラス読み込みプロセス、つまりWebappClassLoaderBase#loadClassのロジックは次のとおりです。

1)最初にresourceEntriesをローカルにキャッシュし、ロードされている場合は、キャッシュ内のデータを直接返します。

2)JVMがクラスをロードしたかどうかを確認し、ロードされている場合は直接戻ります。

3)ロードするクラスがJava SEクラスであるかどうかを確認し、そうである場合は、BootStrapクラスローダーを使用してクラスをロードし、WebアプリケーションのクラスがJavaSEのクラスを上書きしないようにします。

たとえば、java.lang.Stringクラスを作成し、それを現在のアプリケーションの/ WEB-INF / classesに配置します。このステップの保証がない場合、プロジェクトで使用されるStringクラスは自分で定義されます。以下のrt.jarではなく、多くの隠れた危険につながる可能性があります。

4)デリゲート属性デリゲート表示がtrueに設定されている場合、または一部の特別なクラス(javax、orgパッケージの一部のクラス)の場合、親委任モードを使用してロードし、一部のパーツのみが親委任モデルを使用してロードします。

5)ローカルからクラスをロードしてみます。ステップ5でロードが失敗した場合は、このステップに進みます。これにより、親委任モデルが破損し、最初にローカルからロードされます。

7)ここに行くと、ステップ6のロードが失敗したことを意味します。親委任モードが以前に使用されていなかった場合は、ロードを試みるために親クラスローダーに委任されます。

8)このようにすると、ロードのすべての試行が失敗し、ClassNotFoundExceptionがスローされます。

10.スレッドコンテキストクラスローダーを使用したJDBCの原則

JDBC関数に関連する基本クラスはJavaによって一律に定義されています。BootstrapClassLoaderによってロードされるDriverManagerなどのrt.jarでは、JDBCの実装クラスはさまざまなメーカーの実装jarパッケージに含まれています。たとえば、MySQLはmysql。-connector-javaでは、oracleとsqlserverにも独自の実装jarがあります。

このとき、JDBCの基本クラスは、他のメーカーによって実装され、アプリケーションのClassPathの下にデプロイされたJDBCサービスプロバイダーインターフェイス(SPI、サービスプロバイダーインターフェイス)のコードを呼び出す必要があります。クラスAがクラスBを呼び出すと、クラスBはクラスAのクラスローダーによってロードされ、JDBCの基本クラスはBootstrap ClassLoaderによってロードされますが、Bootstrap ClassLoaderはこれらのメーカーが実装したコードを認識せず、ロードしません。

したがって、Javaはスレッドコンテキストクラスローダーを提供し、Thread#setContextClassLoader / Thread#getContextClassLoader()を介して現在のスレッドのコンテキストクラスローダーを設定および取得できるようにします。スレッドの作成時に設定されていない場合は、親スレッドを継承します。アプリケーションのグローバルスコープに設定されていない場合、クラスローダーはデフォルトでアプリケーションクラスローダーになります。

要約すると、JDBCは、親クラスローダーがサブクラスローダーを「委任」してスレッドコンテキストクラスローダーを介してクラスのロードを完了する動作を実現できます。これは明らかに親委任モデルに準拠していませんが、これは親委任の欠点でもあります。モデル自体が原因です。

最後に

私は、オリジナルのテクニカルドライグッズを共有することを主張するプログラマーのJonHuiです。

おすすめ

転載: blog.csdn.net/v123411739/article/details/119700990