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の書き方)

推薦する

MySQL innodb例外の修復に関する経験の共有

テスト用の MySQL ライブラリのセット。以前使用されていたバージョンは、centos6 のデフォ...

VUE + OPENLAYERSがリアルタイムポジショニング機能を実現

目次序文1. ラベルスタイルを定義する2. GeoJSONデータのシミュレーション3. Vercto...

さまざまなターミナルで Mac が SSH 経由でリモート サーバーに接続する方法の説明

Macはシェル(ターミナル)SSHを使用してリモートサーバーに接続します前提条件: 接続する必要があ...

HTML 学習ノート - HTML 構文の詳細な説明 (必読)

1. HTML マークアップ言語とは何ですか? HTML は、Web ページの情報を表すマークアッ...

Ubuntu サーバーで MySQL を設定し、リモート接続を実装する方法

サーバー: Ubuntu Server 16.04 LSSクライアント: Ubuntu 16.04 ...

さようなら Docker: 5 分で Containerd に移行する方法

Docker は非常に人気のあるコンテナ技術です。K8S によって廃止され、別のコンテナ技術である ...

formData 形式タイプを使用してファイルをアップロードする Vue の例

Vue では、一般的にフロントエンドとバックエンドを分離したプロジェクトがあり、データ操作を実装する...

Vue における v-for のキーの一意性の詳細な説明

目次1. DOM の違い2. 同じレイヤーの同じタイプの要素にキー属性を追加する3. キーはインデッ...

誰もが登録できるようにJiedaibaoを宣伝するにはどうすればよいでしょうか? ジエダイバオのプロモーション方法とスキル

借財宝は最近人気が出ている携帯電話ローンソフトウェアプラットフォームです。知人同士の貸し借りが特徴で...

Web ページのスクロール バーが右側に設定されているのはなぜですか?

私たちが毎日使っているブラウザや Word 文書のスクロール バーはなぜ右側にあるのでしょうか。多く...

MySql8.0バージョンに接続するMyBatisの設定問題について

mybatis を学習しているときにエラーが発生しました。エラーの内容は次のとおりです。データベース...

Nginx を使用してクロスドメイン Vue 開発環境を処理する方法

1. 需要正しい Cookie 配信と SSO テストを確実に実行できるように、ローカル テスト ド...

最も完全なpackage.json分析

目次1. 概要2. 名前フィールド3. バージョンフィールド4. 説明フィールド5. キーワードフィ...

行の高さと垂直方向の配置に関する包括的な理解

前の単語line-height、font-size、vertical-align は、インライン要素...

Mysql ファジークエリが大文字と小文字を区別するかどうかの詳細な調査

序文最近、私は小さな個人ブログ プロジェクトを書くのに忙しくしています。 「グローバル検索」機能を実...