MainClassLoader
Java Web アプリケーション (特に、親クラスローダーが である Tomcat 7) の上にある、here と呼ばれるカスタム クラスローダーがありWebAppClassLoader
ます。このカスタム クラスローダーは、Web アプリケーションの TCCL として設定されます。その目的は、クラスパス リソース (クラスおよび非クラス リソースを含む) のルックアップを他のカスタム クラスローダーのセットに委譲することです。応用。(MainClassLoader
それ自体は何もロードしません。)
MainClassLoader.loadClass()
親優先の委譲を行いClassNotFoundException
、プラグイン可能な子クラスローダーを 1 つずつ調べて、どれが結果を提供するかを確認します。それらのいずれもできない場合は、ClassNotFoundException
.
ただし、ここでのロジックはもう少し複雑であり、エンド ユーザーがプラグインされたこれらの子モジュールをいくつか (10 秒単位で) 持つ可能性があるという事実と組み合わせると、クラスローダーが最終的にそのうちの 1 つになることがわかります。今日のJavaがリフレクションベースのコマンドパターンの実装にどれだけ依存しているかを考えると、アプリケーションのよりCPUを集中的に使用する部分。Class.forName()
(つまり、実行時にクラスをロードしてインスタンス化するための呼び出しがたくさんあるということです。)
私たちは、アプリケーションの定期的なスレッド ダンプで最初にこれに気付き始めました。これは、「動作中」のアプリケーションをキャッチしてアプリケーションが何をしているかを確認し、JProfiler を使用して、必要以上に遅いことが知られている特定のユース ケースをプロファイリングすることです。
( を含む) 呼び出しMainClassLoader
の結果が弱い値 (文字列 className によってキー付けされた) を持つ同時マップにキャッシュされる非常に単純なキャッシュ アプローチを作成しました。このクラスのパフォーマンスは完全に低下するほど高くなりました。 JProfiler のホット スポット リスト。loadClass()
ClassNotFoundException
ただ、本当に安全にできるのか心配です。そのようなキャッシュは、意図したクラスローダー ロジックの邪魔になりますか? これを行う際に予想される落とし穴は何ですか?
私が予想するいくつかの明白なもの:
(1) メモリ - 明らかに、このキャッシュはメモリを消費します。無制限のままにしておくと、メモリが枯渇する可能性があります。制限されたキャッシュ サイズを使用してこれに対処できます (このキャッシュには Google の Guava CacheBuilder を使用しています)。
(2) 動的なクラスローディング、特に開発中 - したがって、キャッシュの結果が古くなった後に新しいまたは更新されたクラス/リソースがクラスパスに追加されると、システムが混乱しClassNotFoundExceptions
、クラスが今すべき時にスローされる可能性が高くなります。ロード可能であること。ここでは、キャッシュされた「見つかりません」状態要素の小さな TTL が役立つかもしれませんが、私のより大きな懸念は、開発中に、クラスを更新して JVM にホットスワップしたときに何が起こるかということです。このクラスは、ほとんどの場合、MainClassLoader
そのため、そのキャッシュには古い (古い) バージョンのクラスが含まれている可能性があります。ただし、弱い値を使用しているため、これはこれを軽減するのに役立ちますか? 弱い参照についての私の理解では、GC がそれらを再利用することを決定するパスを実行するまで、コレクションの資格がある場合でもそれらは消えません。
これらは、このアプローチに関する私の 2 つの既知の問題/懸念事項ですが、クラスローディングは、ここで非標準的なことを行うときに落とし穴に満ちた、ちょっとした黒魔術 (暗黒科学ではないにしても) であるということです。
では、私が心配すべきではない、私が心配していないことは何ですか?
更新/編集
上記でプロトタイプを作成したように、最終的にはローカル キャッシングを行わないことを選択しました (JVM によって行われるキャッシング/最適化では危険で冗長に思えます) が、loadClass() メソッド内でいくつかの最適化を行いました。基本的に、この loadClass() メソッドにあるロジック (以下のコメントを参照) は、「カスタマイズ」モジュールが配置されていない場合など、可能な場合にコードを介して「最良のケース」のパスをたどりませんでした。ありましたが、そのクラスローダーに ClassNotFoundException をスローさせ、それをキャッチして次のチェックを行いました。このパターンは、特定のクラス ロード操作がほぼ常に少なくとも 3 つの try/catch ブロックを通過し、それぞれで ClassNotFoundException がスローされることを意味していました。すごく高価。
ただし、問題を解決し続けるために、元の質問にコメントしたいと思います。
既にリストしたもの以外に、カスタム クラスローダーで独自のキャッシングを行う際の懸念事項は何ですか?