Tomcatのクラスロードメカニズムを説明する記事

Tomcatのクラスロードメカニズムを説明する記事

- 序文 -

Apache Tomcat のクラスローディングメカニズムを理解していますか?この記事では、基礎となる原則から始めて、Tomcat クラスのロードに関係するソース コード、メカニズム、ソリューションを徹底的に明らかにし、Tomcat クラスのロードの核心を深く理解できるようにします。

- JVM クラスローダー -

1. JVMクラスローダー

Tomcat クラス ローダーについて言えば、次の図に示すように、JVM クラス ローダーについても簡単に説明する必要があります。

  • Bootstrap ClassLoader: JVM によって提供される基本的な実行クラス、つまり %JAVA_HOME%/jre/lib ディレクトリにあるコア クラス ライブラリをロードするために使用されます。
  • 拡張クラスローダー: Java が提供する標準拡張メカニズムである拡張クラスローダーは、コア クラス ライブラリ以外の Jar パッケージをロードするために使用されます。つまり、指定された拡張ディレクトリ (複数可) に Jar がコピーされている限り、JVM はそれを自動的にロードします (-classpath で指定する必要はありません)。デフォルトの拡張ディレクトリは %JAVA_HOME% と e/lib/ext です。一般的なアプリケーション シナリオでは、Java はこのクラス ローダーを使用して、JVM によってデフォルトで提供されるがコア クラス ライブラリには属さない Jar をロードします。アプリケーションが依存するクラス ライブラリを拡張ディレクトリに配置することは推奨されません。このディレクトリ内のクラス ライブラリは、JVM に基づいて実行されているすべてのアプリケーションから参照できるためです。
  • アプリケーション クラスローダー: アプリケーション クラスローダーは、環境変数 CLASSPATH (非推奨) または -classpath ランタイム パラメータで指定されたディレクトリに Jar パッケージをロードするために使用されます。システム クラス ローダーは通常、アプリケーション Jar パッケージとそのスタートアップ エントリ クラスをロードするために使用されます (Tomcat の Bootstrap クラスはシステム クラス ローダーによってロードされます)。

これらのクラス ローダーの動作原理は同じですが、違いはロード パスが異なること、つまり findClass メソッドによって検索されるパスが異なることです。

親委任メカニズムは、Java クラスが JVM 内で一意であることを保証するものです。Object クラスなど、JRE コア クラスと同じ名前のクラスを誤って記述した場合、親委任メカニズムにより、記述した Object クラスではなく、JRE 内の Object クラスがロードされることが保証されます。

これは、AppClassLoader が Object クラスをロードするときに、ロードを ExtClassLoader に委任し、ExtClassLoader が BootstrapClassLoader に委任するためです。BootstrapClassLoader は Object クラスがすでにロードされていることを検出し、作成した Object クラスをロードせずに直接戻ります。

ここで、クラス ローダーの親子関係は継承を通じて実装されないことに注意してください。たとえば、AppClassLoader は ExtClassLoader のサブクラスではありませんが、AppClassLoader の親メンバー変数は ExtClassLoader オブジェクトを指します。同様に、クラス ローダーをカスタマイズする場合は、AppClassLoader を継承するのではなく、ClassLoader 抽象クラスを継承し、findClass メソッドと loadClass メソッドを書き換えます。Tomcat は、カスタム クラス ローダーを通じて独自のクラス ローディング ロジックを実装します。親委任メカニズムを破壊したい場合は、loadClass メソッドを書き直す必要があることに気付いたかどうかわかりません。これは、loadClass のデフォルトの実装が親委任メカニズムであるためです。

2. クラスローダーのソースコード

パブリック抽象クラス ClassLoader {
  // 各クラスローダーには親ローダーがあります private final ClassLoader parent;
  パブリック Class<?> loadClass(String name) は ClassNotFoundException をスローします {
        loadClass(name, false) を返します。
    }
     保護されたクラス<?> loadClass(文字列名、ブール値解決)
        ClassNotFoundException をスローします
    {
            // まず、クラスがすでにロードされているかどうかを確認します
            クラス<?> c = findLoadedClass(名前);
           // ロードされていない場合 if (c == null) {
                親が null ではない場合
                  // 最初に親ローダーにロードを委任します。これは再帰呼び出しであることに注意してください。c = parent.loadClass(name, false);
                } それ以外 {
                 // 親ローダーが空の場合は、Bootstrap ローダーがロードされているかどうかを確認します c = findBootstrapClassOrNull(name);
                }
              
            // 親ローダーがロードに失敗した場合は、独自のfindClassを呼び出してロードします。if (c == null) {        
                    クラス名を検索します。
                }
            } 
        
            c を返します。
        }
        
    }
    //ClassLoader の findClass メソッドはサブクラスでオーバーライドする必要があります。次のコードは対応するコードです。protected Class<?> findClass(String name){
       //1. 渡されたクラス名に従って、特定のディレクトリ内のクラス ファイルを検索し、.class ファイルをメモリに読み込みます...
       //2. バイト配列を Class オブジェクトに変換するには、defineClass を呼び出します。 return defineClass(buf, off, len);
    }
      // バイトコード配列をクラスオブジェクトに解析し、ネイティブメソッドで実装します。protected final Class<?> defineClass(byte[] b, int off, int len){
    
    }
    
}

カスタム クラス ローダーでは、ClassLoader の loadClass メソッドを書き換える必要があります。

- Tomcatのクラスロードメカニズム -

1. 荷重機構の特徴

分離: Web アプリケーション ライブラリは相互に分離されており、依存するライブラリやアプリケーション パッケージが相互に影響を与えないようにします。 2 つの Web アプリケーションがあり、1 つは Spring 2.5 を使用し、もう 1 つは Spring 4.0 を使用し、アプリケーション サーバーが 1 つのクラス ローダーを使用してロードする場合、Jar パッケージの上書きが原因で Web アプリケーションが正常に起動しなくなると想像してください。

柔軟性: Web アプリケーション間のクラス ローダーは互いに独立しているため、1 つの Web アプリケーションのみを再デプロイでき、その Web アプリケーションのクラス ローダーは他の Web アプリケーションに影響を与えずに再作成されます。クラス ローダーを使用する場合、これは明らかに不可能です。クラス ローダーが 1 つしかない場合、クラス間の依存関係が整理されず、Web アプリケーションのクラスを完全に削除することは不可能だからです。

パフォーマンス: 各 Web アプリケーションにはクラス ローダーがあるため、Web アプリケーションはクラスをロードするときに他の Web アプリケーションに含まれる Jar パッケージを検索しません。アプリケーション サーバーにクラス ローダーが 1 つしかない場合よりも、パフォーマンスは当然高くなります。

2. Tomcatクラスローディングソリューション

  • ブートストラップ クラス ローダーと拡張クラス ローダーの役割は変更されません。
  • システム クラス ローダーは通常、CLASSPATH の下のクラスをロードしますが、Tomcat の起動スクリプトはこの変数を使用しません。代わりに、bootstrap.jar など、通常 catalina.bat または catalina.sh で指定される Tomcat が起動するクラスをロードします。 CATALINA_HOME/bin にあります。
  • 共通クラス ローダーは、CATALINA_HOME/lib にある、servlet-api.jar など、Tomcat およびアプリケーションでよく使用されるいくつかのクラスをロードします。
  • Catalina ClassLoader は、サーバー内の表示可能なクラスをロードするために使用されます。これらのクラスにはアプリケーションからアクセスできません。
  • SharedClassLoader は、クラス サーバーに依存しないアプリケーション共有クラスをロードするために使用されます。
  • WebappClassLoader では、各アプリケーションに固有の Webapp ClassLoader があり、このアプリケーションの /WEB-INF/classes および /WEB-INF/lib の下のクラスをロードするために使用されます。

Tomcat 8.5 では、デフォルトで厳密な親委任メカニズムが変更されました。

  • キャッシュから読み込みます。
  • キャッシュ内にそのようなファイルがない場合、最初に ExtClassLoader が呼び出されてロードされます。拡張クラス ローダーは親の委任に従います。bootstrap を呼び出して対応するライブラリが存在するかどうかを確認し、その後、ExtClassLoader にフォールバックして拡張パッケージのデータをロードします。
  • ロードされていない場合は、/WEB-INF/classes からロードします。
  • ロードされていない場合は、/WEB-INF/lib/*.jar からロードします。ロードされていない場合は、WebAppclassLoader は SharedClassLoader に委任し、SharedClassLoader は CommonClassLoader に委任し、その後 BootstrapClassLoader に委任します。次に、BootstrapClassLoader は自身のディレクトリで対応するクラスを検索します。見つかった場合は、それをロードします。見つからない場合は、次のレベルの ExtClassLoader に委任します。次に、ExtClassLoader は自身のディレクトリでクラスを検索します。見つかった場合は、それをロードします。見つからない場合は、次のレベルに委任し、親の委任原則に従います。

3. アプリケーションクラスローダーの読み込みプロセスを分析する

アプリケーション クラス ローダーは WebappClassLoader であり、その loadClass は親クラス WebappClassLoaderBase にあります。

  パブリック Class<?> loadClass(String name, boolean resolve) は ClassNotFoundException をスローします {
        同期化 (getClassLoadingLock(名前)) {
            (log.isDebugEnabled())の場合
                log.debug("loadClass(" + name + ", " + resolution + ")");
            クラス<?> clazz = null;
            // 停止したクラスローダーへのアクセスをログに記録する
            checkStateForClassLoading(名前);    
            //現在の ClassLoader のローカル キャッシュからクラスをロードし、見つかった場合は返します。clazz = findLoadedClass0(name);
            (clazz != null)の場合{
                (log.isDebugEnabled())の場合
                    log.debug("キャッシュからクラスを返す");
                もし(解決する)
                    クラスを解決します(clazz);
                戻りクラッズ;
            }
            // ローカル キャッシュがない場合は、ClassLoader の findLoadedClass メソッドを呼び出して、JVM がこのクラスをロードしたかどうかを確認します。ロードした場合は、直接戻ります。
            clazz = findLoadedClass(名前);
            (clazz != null)の場合{
                (log.isDebugEnabled())の場合
                    log.debug("キャッシュからクラスを返す");
                もし(解決する)
                    クラスを解決します(clazz);
                戻りクラッズ;
            }
            文字列リソース名 = binaryNameToPath(name, false);
            //この時点では、javaseClassLoader は拡張クラスローダーであり、拡張クラスローダーは javaseClassLoader に割り当てられます
            クラスローダー javaseLoader = getJavaseClassLoader();
            ブール値の tryLoadingFromJavaseLoader;
            試す {
              .....
            // getResource で取得できれば // 拡張クラスローダーの getResource で取得できれば、拡張クラスローダーでロードできるということになります。次に、拡張クラスローダーがロードできるように手配します if (tryLoadingFromJavaseLoader) {
                試す {
                    //拡張クラスローダーを使用してロードします clazz = javaseLoader.loadClass(name);
                    (clazz != null)の場合{
                        もし(解決する)
                            クラスを解決します(clazz);
                        戻りクラッズ;
                    }
                } キャッチ (ClassNotFoundException e) {
                    // 無視する
                }
            }
            // (0.5) SecurityManager 使用時にこのクラスにアクセスする権限
            セキュリティマネージャが null の場合
                int i = name.lastIndexOf('.');
                もし (i >= 0) {
                    試す {
                        セキュリティマネージャのパッケージアクセスをチェックします(名前のサブ文字列(0,i));
                    } キャッチ (SecurityException se) {
                        文字列エラー = "セキュリティ違反、" + を使用しようとしました
                            「制限クラス: 」+ 名前;
                        log.info(エラー、se);
                        新しい ClassNotFoundException(error, se) をスローします。
                    }
                }
            }
            ブール型 delegateLoad = delegate || filter(name, true);
            // (1) 要求があれば親に委任する
            //trueの場合、親クラスローダーを使用してロードします。if (delegateLoad) {
                (log.isDebugEnabled())の場合
                    log.debug("親クラスローダー1に委任しています" + parent);
                試す {
                    clazz = Class.forName(名前、false、親);
                    (clazz != null)の場合{
                        (log.isDebugEnabled())の場合
                            log.debug("親からクラスをロードしています");
                        もし(解決する)
                            クラスを解決します(clazz);
                        戻りクラッズ;
                    }
                } キャッチ (ClassNotFoundException e) {
                    // 無視する
                }
            }
            // (2) ローカルリポジトリを検索
            (log.isDebugEnabled())の場合
                log.debug("ローカルリポジトリを検索しています");
            試す {
                // ローカルにロード clazz = findClass(name);
                (clazz != null)の場合{
                    (log.isDebugEnabled())の場合
                        log.debug("ローカルリポジトリからクラスをロードしています");
                    もし(解決する)
                        クラスを解決します(clazz);
                    戻りクラッズ;
                }
            } キャッチ (ClassNotFoundException e) {
                // 無視する
            }
            // (3) 無条件に親に委譲する
            //まだロードされていません。親クラスローダーを使用して再度ロードしてみてください。if (!delegateLoad) {
                    (log.isDebugEnabled())の場合
                    log.debug(" 最後に親クラスローダーに委任しています: " + parent);
                試す {
                    clazz = Class.forName(名前、false、親);
                    (clazz != null)の場合{
                        (log.isDebugEnabled())の場合
                            log.debug("親からクラスをロードしています");
                        もし(解決する)
                            クラスを解決します(clazz);
                        戻りクラッズ;
                    }
                } キャッチ (ClassNotFoundException e) {
                    // 無視する
                }
            }
        }
        新しい ClassNotFoundException(名前) をスローします。
    }

注: 英語のコメントの 37 行目には、システム クラス ローダーが取得されたと記載されていますが、デバッグしてみると、拡張クラス ローダーであることがわかります。実際には、ロードするクラスが拡張クラス ローダー パスの下にすでに存在する場合、システム クラス ローダーを直接呼び出すのは間違っているため、拡張クラス ローダーであるはずだと推測できます。次の図は、デバッグ後に取得されたクラス ローダーの検証を示しています。

要約する

Tomcat は親委任の原則に違反しており、実際にはアプリケーション クラス ローダー内の親委任を破壊していますが、他のクラス ローダーは依然として親委任に従っています。

Tomcat クラスローディングメカニズムに関するこの記事はこれで終わりです。Tomcat クラスローディングメカニズムの詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • Tomcatのクラスロードメカニズムのプロセスとソースコード分析

<<:  HTMLテーブルレイアウトの実践的な使い方の詳しい説明

>>:  IE6かどうかを判定する最短JS(IEの書き方)

推薦する

Docker ファイルの保存パス、ポート マッピング操作モードの変更

コンテナの起動コマンドを取得する方法コンテナはすでに作成されていますが、その起動パラメータ(データが...

MYSQL データベースの基礎 - 結合操作の原理

結合では、ネスト ループ結合アルゴリズムが使用されます。ネスト ループ結合には 3 つの種類がありま...

Vue は div の高さをドラッグ可能にします

この記事では、divのドラッグ可能な高さを実現するためのVueの具体的なコードを参考までに共有します...

HTML Webページの例を使用してヘッドエリアコードの意味を説明する

例を使って、Webページのヘッダー情報の意味を理解しましょう。 <!DOCTYPE HTML ...

IE6では画像要素imgに余分な空白スペースがある

ページの DIV+CSS レイアウトを行う際、IE6 で画像要素 img の下に余分なスペースができ...

JS を使用して Web ページのウォーターフォール レイアウトを実装する方法

目次序文:ウォーターフォールレイアウトとは何ですか?達成方法: 1. 画像を取得する2. 画像の帯域...

Tomcatc3p0 で jnid データ ソースを構成する 2 つの実装方法の分析

c3p0の使用c3p0jarパッケージをインポートする <!-- https://mvnrep...

Dockerイメージが消える問題を解決する

1. 50と93では鏡像が消える [root@h50 /]# df -h ファイルシステムの使用済み...

MySQL からエクスポートされた scv ファイル内の文字化けやジャンプ行の問題をすばやく解決します

仕事上の理由により、完全なオンライン化(​​つまり、すべてのデータがオンラインで完了し、インポートや...

Centos8 システムの VMware インストール チュートリアル図 (中国語グラフィカル モード)

目次1. ソフトウェアとシステムイメージ2. 仮想マシンを作成する3. CentOS8をインストール...

MySQL インデックスの種類 (通常、ユニーク、フルテキスト) の説明

MySQL のインデックスの種類には、通常のインデックス、一意のインデックス、全文インデックスがあり...

MySQL で準備、実行、割り当て解除ステートメントを使用するチュートリアル

序文MySQLでは、準備、実行、割り当て解除を正式にはPREPARE STATEMENTと呼びます。...

CocosCreatorでシューティングゲームを作る詳しい解説

目次シーン設定ゲームリソース砲塔の回転動的に生成された弾丸衝突計算効果を高めるターゲットの動き弾薬庫...

MySQLのGROUP BYステートメントを最適化する方法

MySQL で、id、a、b の 3 つのフィールドを持つ新しいテーブルを作成します。次のように、同...

Vueでファジークエリを実装する方法の簡単な例

序文いわゆるファジークエリとは、ユーザーの完全な入力やすべての入力情報がなくてもクエリサービスを提供...