【JVMをゼロから学ぶ | 第4回】クラスローダーの分類と親委任の仕組み

序文:

Java プログラミングでは、クラス ローダー (クラス ローダー) が重要な役割を果たします。クラス ローダーは Java バイトコードをロードし、それを実行可能オブジェクトに変換する役割を担っており、アプリケーション内でさまざまなクラスやリソースを使用できるようにします。 Java クラス ローダーの設計と実装は、動的な拡張とモジュラー プログラミングをサポートし、Java 言語に優れた柔軟性と保守性を提供することを目的としています。

クラスローダーの分類は、Java クラスロードメカニズムを理解する上で重要な部分です。さまざまなタイプのクラスローダーがさまざまなタイプのクラスのロードを担当し、ロード戦略と優先順位も異なります。この記事では、クラスローダーの分類を掘り下げ、各クラスローダーの特徴や利用シーンを詳しく紹介します。

目次

序文:

クラスローダー: 

クラスローダーとは: 

クラスローダーのアプリケーションシナリオ:

クラスローダーの分類 (JDK8 および以前のバージョン): 

親委任メカニズム:

親の委任メカニズムとは次のとおりです。

親委任メカニズムのソース コードの解釈:

親委任メカニズムの役割:

親の委任メカニズムを破壊する:

要約:


 

クラスローダー: 

クラスローダーとは: 

ClassLoader は、クラスおよびインターフェイスのバイトコード データを実装するために、JAVA 仮想マシンによってアプリケーションに提供されるテクノロジーです。

クラス ローダーは、バイトコードが取得されてメモリにロードされるロード プロセスの部分にのみ参加します。

クラスローダーのアプリケーションシナリオ:

  1. 動的ロード: クラス ローダーを使用すると、新しいクラスとリソースを実行時に動的にロードできます。これは、特定の条件やユーザーのニーズに基づいてさまざまなクラスをロードする必要があるアプリケーションに役立ちます。たとえば、クラス ローダーは、プラグイン システムやモジュール開発でモジュールを動的にロードおよびアンロードして、柔軟な拡張や機能のカスタマイズを実現するためによく使用されます。

  2. ホット デプロイメント: クラス ローダーはホット デプロイメント、つまりアプリケーションの実行中にロードされたクラスの置換と更新を実装できます。これにより、アプリケーションを停止せずにコードの更新や修正を行うことができます。ホット デプロイメントは開発およびデバッグ段階で非常に役立ち、開発効率とデバッグ エクスペリエンスを向上させます。

  3. バージョン分離: 異なるクラス ローダーを使用して異なるバージョンのクラスをロードすることで、クラスのバージョン分離を実現できます。これは、クラスの異なるバージョン間の競合や互換性の問題を回避するために、アプリケーションのアップグレードや依存関係の管理において非常に重要です。

  4. セキュリティ制御: クラス ローダーは、特定のコードやリソースへのアクセスを制限または制御するセキュリティ ポリシーを適用できます。カスタム クラス ローダーを介して、カスタム セキュリティ ポリシーを実装し、読み込まれたクラスに対して権限の制御と検証を実行できます。

  5. バイトコードの拡張機能と動的プロキシ: クラス ローダーを使用して、バイトコードの拡張機能と動的プロキシを実装できます。カスタム クラス ローダーを使用すると、クラス ロード プロセス中にバイトコードを変更し、追加のロジックや機能を追加できます。これには、AOP (アスペクト指向プログラミング) やプロキシ パターンなどのテクノロジに重要な用途があります。

クラス ローダーの親委任メカニズムも、面接で必ず尋ねるべき質問です。面接官が JVM について質問している限り、-関連コンテンツの場合は、親委任メカニズムを必ず確認する必要があります。したがって、クラスローダー関連の内容をよく学ぶ必要があります。

クラス ローダーとは何か、およびその適用シナリオを理解したら、クラス ローダーについて正式に学習しましょう。


クラスローダーの分類 (JDK8 および以前のバージョン): 

  1. ブートストラップ クラスローダー: JVM の一部であり、java.lang パッケージ内のクラスなどの Java コア クラス ライブラリをロードします。これはトップレベルのクラス ローダーであり、通常はC++ を使用して実装され、Java コードで直接取得することはできません< /span>。 (一般的かつ重要)

  2. Extension ClassLoader: Java 拡張ライブラリのロードを担当します。通常は JRE の lib/ext ディレクトリにあります。これは Java で書かれており、スタートアップ クラス ローダーによってロードされます。 (一般的ですが重要ではありません)

  3. アプリケーション クラスローダー: システム クラス ローダー (System ClassLoader) とも呼ばれ、ユーザー クラス パス (Classpath) で指定されたクラス ライブラリをロードします。これは開発者によって最も一般的に使用されるクラス ローダーであり、デフォルトのクラス ローダーでもあります。

上記の 3 つの一般的なクラス ローダーに加えて、java.lang.ClassLoader クラスを継承してクラス ローダーをカスタマイズすることもできます。カスタム クラス ローダーは、ネットワークからのクラス ファイルのダウンロード、復号化など、さまざまな特定のニーズを満たすためにクラスを柔軟にロードできます。

要約すると、一般的なクラス ローダーは、起動クラス ローダー、拡張クラス ローダー、およびアプリケーション クラス ローダーに分類できます。開発者は、必要に応じてクラスローダーをカスタマイズすることもできます。

実際の JAVA コードでは、複数のクラス ローダーのロード スコープに JAR パッケージが同時に存在する状況が発生することがありますが、このとき、この問題を解決するには親の委任メカニズムが必要です。

親委任メカニズム:

親の委任メカニズムとは次のとおりです。

親委任モデルは、Java クラス ローダーの動作メソッドであり、クラス ロードの安全性を確保するために使用されます。 < a i=3>そして一貫性

親委任メカニズムに従って、クラス ローダーがクラスをロードする必要がある場合、まず親クラス ローダーに委任してロードを試行します。親クラス ローダーがクラスを正常にロードできる場合、クラスの Class オブジェクトが返されます。親クラス ローダーがクラスをロードできない場合、子クラス ローダーはそれ自体をロードしようとします。

 

簡単に言うと、親委任メカニズムの核心は、誰がクラスをロードするかという問題を解決することです。このプロセスでは、ロードされているかどうかを調べ、ロードを試みます。

注: 「親」は存在しますが、ローダーとその親クラス ローダーの間の関係は継承とみなされません。本質は、ローダー内に ClassLoader を作成して、その親クラス ローダーを保存することです。​ 

拡張クラス ローダーの親オブジェクトを取得しようすると、結果は null になることに注意してください。これは、その親クラスが起動クラス ローダーではないということではなく、起動クラス ローダーは JVM の一部であり、C++ で実装されており、取得できないためです。 =4>!

親委任メカニズムのソース コードの解釈:

親委任メカニズム全体はクラスロードで実行されるため、主にソース コードのこの部分を確認します。

 クラスをロードしようとするとき、loadClass メソッドを呼び出します。メソッドの最初のパラメータは、ロードされたクラスの名前です。2 番目のパラメータは、クラスを解析するかどうかです

loadClassメソッドを入力します

 実際、このコードは比較的理解しやすく、全体的なロジックは次のとおりです。

  1. findLoadedClass を使用して、ターゲット クラスがロードされているかどうかを確認します。
  2. ターゲットクラスがロードされていない場合 (c==null)、現在のローダーの親クラスローダーを探します。親クラスローダーがある場合 (parent!=null)、現在のクラスを親クラスに渡します。ローダーでloadClass.メソッドを実行します。親クラス ローダーがない場合は、起動クラス ローダー (BootstrapClassLoad) に親クラス ローダーを見つけてロードさせます。
  3. 最上位ローダーに到達した後もターゲット クラスをロードできない場合は、現在のローダーにそれをロードさせ (c=findClass(name))、時間とその他の情報を記録して、0 を返します。
  4. ターゲットクラスがロードされている場合は、直接 0 を返します。

最後に、c=findClass(name) の findClass ソース コードを見てみましょう。

親委任メカニズムの役割:

  1. 重複ロードを避ける: 親委任メカニズムを使用すると、各クラス ローダーはクラスをロードする前に親クラス ローダーに委任します。これにより、同じクラスが複数の異なるクラス ローダーによってロードされるのを防ぎ、クラスの一貫性を確保し、繰り返しのロードによって発生する競合やメモリの無駄を回避できます。

  2. セキュリティ保証: コア クラス ライブラリ (Java のコア クラス ライブラリなど) はスタートアップ クラス ローダーによってロードされ、ユーザー定義クラスはアプリケーション クラス ローダーによってロードされます。 。これにより、コア クラス ライブラリのセキュリティが確保され、ユーザー定義クラスによるコア クラス ライブラリの動作の改ざんを防ぐことができます。

  3. クラスの分離: 異なるクラス ローダーによってロードされたクラスは、異なる名前空間に配置され、相互に分離されます。 2 つのクラスの完全修飾名が同じであっても、異なるクラス ローダーによってロードされたクラスは、JVM では異なるクラスとみなされます。この分離によりクラスの競合が効果的に回避され、各クラス ローダーがクラスを個別にロードして管理できるようになります。

  4. 拡張性: クラス ローダーをカスタマイズすることで、Java のクラス ロード メカニズムを拡張して、特定のロード要件を達成できます。開発者はクラスローダーをカスタマイズして、ホットデプロイメントや動的ローディングなどの機能を実装できます。カスタム クラス ローダーは、親クラス ローダーの特性を継承し、ビジネス ニーズに応じて拡張できます。

一般に、親委任メカニズムは、クラスの一貫性、セキュリティ、分離を確保し、繰り返しロードを回避することができ、また、特定のニーズに応じてクラス ローダーをカスタマイズできるように柔軟な拡張性も提供します。

親委任メカニズムは、JAVA クラスのロードに優れた セキュリティを提供しますが、 および 利便性 a>。しかし、場合によっては、次のような親の委任メカニズムを破らなければならない場合もあります。

Tomcat コンテナは複数の WEB アプリケーションを実行できます。これら 2 つのアプリケーションに同じ名前のクラス A が存在する場合、Tomcat は両方のクラス A がロードされ、異なるクラスであることを確認する必要があります。親委任メカニズムが壊れていない場合、WEB1 のクラス A が記録された後、WEB2 の自身のクラス A は正常にロードされず、親委任メカニズムに従って、この時点で WEB1 のクラス A が直接返されます。この時点で、親の委任メカニズムを解除する必要があります。

親の委任メカニズムを破壊する:

1. クラスローダーをカスタマイズし、クラスロードを書き換えます (Tomcat の使用方法)。

上記のソース コードの個別の読み取りを通じて、親委任メカニズムの基本的なプロセスを皆さんは理解できたと思います。親の委任メカニズムを壊したい場合は、Classload メソッドを書き換えるだけです。具体的には、次のコード ブロックを書き換えます。

コード例:

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查类是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 检查类是否在系统类加载器中已经加载
                c = findClass(name);
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 在这里实现自定义的类加载逻辑
        // 可以从其他位置加载类的字节码,并使用 defineClass() 方法定义类
    }
}

ただし、このコードのロジックでは、カスタム クラスの親クラス ローダーはロードしませんが、デフォルトの親クラス ローダーも存在することに注意してください。 < a i=1>アプリケーション クラス ローダーですが、loadClass を書き換えたときに親クラス ローダーを使用しませんでした。

ローダーをカスタマイズしていくつかのクラスを個別にロードしたい場合。現時点では、親委任メカニズムを壊さず、FindClass で書き直すことを選択する必要があります。

2. スレッド コンテキスト クラス ローダー (JDBC 使用戦略):

JDBC は、データベースに接続してさまざまなデータベース ドライバを管理し、関連ドライバを読み込むときに、DriveManager というパッケージを使用します。

String url = "jdbc:mysql://localhost:3306/your_database_name";
String username = "your_username";
String password = "your_password";
Connection connection = DriverManager.getConnection(url, username, password);

 DriveManager は rt.jar にあり、スタートアップ クラス ローダーによってロードされます。

このパッケージはさまざまなデータベース ドライバー クラスをロードする必要があります。このサードパーティ パッケージは、アプリケーション読み込みクラスに読み込む必要があります。その後、問題が発生します:

つまり、起動クラス ローダーがDriveManagerをロードした後、ロードする必要があるさまざまなデータベース ドライバが必要になります。< a i=3>スタートアップ クラス ローダーはロードできません。アプリケーション クラス ローダーによってのみロードできます。これは、 親委任メカニズムのボトムアップ委任原則に違反していませんか?

段階的に分析してみましょう。まず、SPI メカニズムを紹介します。

        SPI (Service Provider Interface) は、Java によって提供されるサービス ディスカバリ メカニズムです。これにより、開発者はサービス インターフェイスを定義でき、サードパーティ ベンダーはアプリケーションのクラスパスの下に実装を提供することでアプリケーションの機能を拡張できます。

SPI メカニズムは次のように機能します。 まず、開発者はサービス インターフェイスと、そのインターフェイスのサービス実装を提供する 1 つ以上のクラスを定義します。次に、アプリケーションのクラスパスに構成ファイルを作成します。ファイル名は「META-INF/services/インターフェイスの完全修飾名」である必要があり、インターフェイスの完全修飾名がファイル名として使用されます。 、その内容はサービス インターフェイス実装クラスの完全修飾名です。

アプリケーションが初期化されると、Java ランタイムは Java のリフレクション メカニズムを使用して、クラスパス上の構成ファイルからサービス インターフェイスの実装クラスを読み取り、ロードします。このようにして、アプリケーションは実装クラスのインスタンスを取得し、それが提供する関数を使用できます。

そして、DriveManager は、SPI メカニズムを通じて、JaR パッケージによってロードされるドライバーを迅速に見つけます。

SPI メカニズムに基づいて、DriveManager をロードする全体的なプロセスは次のとおりです。

SPI メカニズムに基づいて DriveManager をロードする全体的なプロセスを理解した後、もう一度考えてみましょう。SPI メカニズムは、親の委任メカニズムの下で下からの委任をどのようにして解除するのでしょうか。毛織物?

SPI メカニズムでは、通常、スレッド コンテキスト クラス ローダー (Thread Context Class Loader) が特定の実装クラスをロードするために使用されます。スレッド コンテキスト クラス ローダーは、各スレッドのクラス ローダーを指定するためにマルチスレッド環境で導入された概念です。スレッド コンテキスト クラス ローダーは、通常、 Thread.currentThread().setContextClassLoader() メソッドを介して設定されます。

SPI メカニズムでは、スレッド コンテキスト クラス ローダーは、親委任モデルの下で委任の問題をボトムアップで解決できます。具体的には、SPI 実装フレームワークのコードがクラス ライブラリに配置され、アプリケーションによってカスタマイズされた SPI 実装クラスがアプリケーションのクラス パスの下に配置されている場合、親の制限により、SPI 実装はアプリケーションによって直接ロードできません。委任モデルの種類。現時点では、SPI 実装クラスは、アプリケーションのスレッド コンテキスト クラス ローダーを使用してロードできます。つまり、スレッド コンテキスト クラス ローダーをアプリケーションのクラス ローダーに設定します< a i =4>。このようにして、SPI 実装フレームワークは、スレッド コンテキスト クラス ローダーを通じてアプリケーションに SPI 実装クラスをロードできるため、親委任モデルの制限が打ち破られます。

SPI メカニズムはスレッド コンテキスト クラス ローダーの正しい設定に依存していることに注意してください。したがって、SPI メカニズムを使用する場合は、SPI 実装フレームワークが正しく機能できるように、スレッド コンテキスト クラス ローダーが正しく設定されていることを確認する必要があります。アプリケーションに SPI 実装クラスをロードします。

簡単に言うと、SPI にはコンテキスト クラス ローダーがあり、アプリケーション クラス ローダーを事前に保存できます。次に、スタートアップ クラス ローダーを使用して DriveManager をロードし、DriveManager がデータベース ドライバーをロードする必要がある場合、DriveManager はコンテキスト クラス ローダーを呼び出し、現在のローダーがスタートアップ クラス ローダーからアプリケーション クラス ローダーに変更されます。

しかし実際には、コンテキスト ローダーが親の委任メカニズムを破る方法についてはまだ議論があります。

  • 一部の人々は、彼が親の委任メカニズムを破ったと考えています: DriveManager は起動クラス ローダーによってロードされますが、記録中に記録するにはプログラム クラス ローダーを委任する必要があるためです。プロセス、親の委任を破る メカニズムの委任はトップダウンのルールです。
  • 一部の人々は、彼が親の委任メカニズムを破っていないと考えています: クラスのロード プロセス全体で、DriveManager は Java コア パッケージ rt.jar 内にあるため、ロードされるからです。起動クラス ローダーによる; jar パッケージ内のデータベース ドライバーはサードパーティ パッケージであるため、アプリケーション クラス ローダーからロードされます。 DriveManager クラスをロードする場合でも、データベース ドライバー クラスをロードする場合でも、loadClass メソッドはオーバーライドされません。ネイティブの loadClass を使用する限り、親の委任メカニズムに従います。

3. OSGI フレームワーク クラス ローダー:

歴史的に、OSGI モジュラー フレームワークは親委任メカニズムを破壊し、ピア間でクラス レコーダーの読み込みを委任していました。

OSGi (Open Services Gateway) は、モジュール式で動的かつ拡張可能な Java アプリケーションを構築するための仕様およびフレームワークです。

モジュール化とは、アプリケーションを複数の独立したモジュール (バンドルとも呼ばれます) に分割し、それぞれに独自のコードとリソースを含めることを指します。このモジュール設計により、開発者はアプリケーションをより柔軟に管理および保守できるようになり、再利用性と保守性が向上します。

初期の頃、JAVA にはモジュール性という概念がありませんでした。すべての jar パッケージはrt.jar で管理されていましたが、 OSGi 同様の機能を持つjarパッケージを1つのjarパッケージにまとめて一元管理する方法を提供します。

OSGi フレームワークでは、各モジュールはバンドルと呼ばれ、バンドルには独自のクラスとリソースを含めることができます。 OSGi は、BundleClassLoader と呼ばれる独自のクラス ローダー実装を使用します。

BundleClassLoader は OSGi フレームワークのコア クラス ローダーであり、クラスをロードするときに親の委任メカニズムを破壊します。まずクラス自体のロードを試みますが、必要なクラスが見つからない場合は、親クラス ローダーに委譲します。 BundleClassLoader は最初に自分自身をロードしようとし、必ずしも親優先の原則に従わないため、このメカニズムは標準の親委任メカニズムとは異なります。

要約:

クラスローダーと親委任メカニズムは、Java の重要な概念です。 Java プログラムは、クラス ローダーを通じてクラスを動的にロードして、コードの柔軟性とスケーラビリティを実現できます。親委任メカニズムにより、クラスの一意性と一貫性が保証され、繰り返しの読み込みと競合が回避されます。クラスローダーと親委任を理解することは、クラスパスの競合を解決し、モジュール開発を実現し、セキュリティを確保するために重要です。これらは、Java の動的読み込み機能とモジュール開発を習得する上で非常に重要です。 Java の原理とメカニズムを深く理解すると、Java の柔軟性と拡張性をより適切に活用し、優れたアプリケーションを開発できます。場合によっては、クラス ローダーの動作をさらに便利にするために、クラス ローダーの動作をカスタマイズおよび拡張することが必要になる場合があります。つまり、クラス ローダーと親委任メカニズムは Java 仮想マシンのコア コンポーネントであり、Java の動的読み込み機能とモジュール開発を理解して習得するために重要です。

私のコンテンツがお役に立ちましたら、いいね、コメント、収集してください。創作するのは簡単なことではありませんが、皆さんのサポートが私の原動力です。

 

 

おすすめ

転載: blog.csdn.net/fckbb/article/details/134842083