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 コマンド例外「権限が拒否されました」の解決方法

Linuxシステムでは、dockerを新しくインストールし、次のようなコマンドを入力します。dock...

MySQLビューの原理と使用法の詳細な説明

この記事では、例を使用して MySQL ビューの原理と使用方法を説明します。ご参考までに、詳細は以下...

Docker Swarm サービス オーケストレーション コマンドの詳細な説明

1. はじめにDocker には、タスクを構成する複数の Docker コンテナをオーケストレーショ...

Centos7.4 サーバーへの Apache のインストールとインストール プロセス中に発生した問題の解決策

この記事では、CentOS 7.4 サーバーに Apache をインストールする方法と、インストール...

docker ベースで Prometheus+Grafana を構築する手順の詳細説明

1. プロメテウスの紹介Prometheus は、もともと SoundCloud によって開発された...

Vueは3段階のナビゲーション表示と非表示を実装します

この記事では、3階層ナビゲーションの表示と非表示を実現するためのVueの具体的なコードを例として紹介...

TinyEditorはシンプルで使いやすいHTML WYSIWYGエディタです

数日前、国産の XHTML エディタを紹介しました。今日は、有名な海外の Web デザイン ブログl...

MySQLのロックについて理解しておくべきこと

1. はじめにMySQL ロックは、その範囲に応じて、グローバル ロック、テーブル ロック、行ロック...

CSS を使用して固定左列と適応右列の 2 列レイアウトを実現する 4 つの方法

1. フロート+オーバーフロー:非表示このメソッドは主にオーバーフローを通じて BFC をトリガーし...

CSS 位置固定左と右の二重配置実装コード

CSS 位置position 属性は、要素の配置タイプを指定します。位置プロパティには 5 つの値が...

MySQL の基本: グループ化関数、集計関数、グループ化クエリの詳細な説明

目次1. 使い方が簡単2. DISTINCTを使用して重複を削除する3. COUNT()の詳細な紹介...

docker-compose で Jenkins をインストールする際の実践的なメモ

ディレクトリを作成する cd /usr/local/docker/ jenkins-docker を...

ブラウザの自動更新を実装するReactサンプルコード

目次フロントエンドルーティングとは何ですか?フロントエンドルーティングを実装するにはどうすればいいで...

Linux での MySQL 8.0.11 のインストールに関するチュートリアル

1. 公式サイトにアクセスしてインストールパッケージをダウンロードしますダウンロードリンク: クリッ...

Vueは書籍ショッピングカートの機能を実現

この記事の例では、書籍ショッピングカート機能を実現するためのVueの具体的なコードを参考までに共有し...