GlassfishV2.1.1で実行されている複雑なアプリケーションがあります。コードを動的にロードできるようにするために、クラスを再定義できるCustomClassloaderを実装しました。動作は非常に簡単です。動的にロードされたクラスが変更されると、CustomClassloaderの現在のインスタンスが「ドロップ」され、必要なクラスを再定義するために新しいインスタンスが作成されます。
これは、同じクラスが数回リロードされた後(したがって、新しいCustomClassloaderが作成されるたびに)、CustomClassloaderの他のインスタンスがガベージコレクションされないため、PermGenスペースエラーが発生することを除いて、うまく機能します。(このクラスのインスタンスは1つだけである必要があります)
リークがどこにあるかを追跡するために、さまざまな方法を試しました。
- visualvm =>ヒープダンプを作成し、CustomClassloaderのすべてのインスタンスを抽出します。いずれも確定していないことがわかります。最も近いGCルートを確認すると、visualvmは存在しないことを通知します(最後のインスタンスを除いて、これは「実際に使用された」ものであるため)。
- jmap / jhat =>ほぼ同じ結果が得られます。CustomClassloaderのすべてのインスタンスが表示され、リンクをクリックしてそのうちの1つの参照がどこにあるかを確認すると、何もないことを意味する空白のページが表示されます。 。
- Eclipse Memory Analyzer Tool =>次のOQLクエリを実行すると、奇妙な結果が得られます。
結果が1つしかないため、明らかに正しくない
SELECT c FROM INSTANCEOF my.package.CustomClassloader c
インスタンスが1つしかないことを示しています。
また、このリンクを確認し、新しいCustomClassloaderが作成されたときにいくつかのリソースリリースを実装しましたが、何も変わりません。PermGenメモリはまだ増え続けています。
ですから、おそらく何かが足りないのですが、ポイント(1-2)と(3)の違いは、私が理解できないことを示しています。何が悪いのかを知るためにどこを見ればいいですか?私が従ったすべてのチュートリアルは、「最も近いGCルートの検索」機能を使用してリークしている参照を検索する方法を示しているため(私の場合はありません)、エラーを追跡する方法がわかりません。
編集1:ヒープダンプの例をここにアップロードしました。アンロードされていないClassLoaderは、次のクエリを使用してvisualvmで選択できますselect s from saierp.core.framework.system.SAITaskClassLoader s
。GCルートがないため、4つのインスタンスがあり、最初の3つが収集されていることがわかります...どこかに参照があるはずですが、私はしません。どうすれば検索できるかわかりません。どんなヒントも歓迎します:)
編集2:いくつかのより深いテストの後、私は非常に奇妙なパターンを見る。リークは、OpenJPAによってロードされているデータに依存しているようです。新しいデータがロードされていない場合は、クラスローダーをGCできますが、そうでない場合はそうではありません。新しいSAITaskClassLoaderを作成して古いものを「クリア」するときに使用するコードは次のとおりです。
PCRegistry.deRegister(cl);
LogFactory.release(cl);
ResourceBundle.clearCache(cl);
Introspector.flushCaches();
=パターン1(クラスローダーはGCされます):=
- 新しいSAITaskClassLoader
- データのロードD1、D2、...、Dn
- 新しいSAITaskClassLoader
- データのロードD1、D2、...、Dn
- ..。
=パターン2(クラスローダーはGCされていません):=
- 新しいSAITaskClassLoader
- データのロードD1、D2、D3
- 新しいSAITaskClassLoader
- データのロードD3、D4、D5
- 新しいSAITaskClassLoader
- データのロードD5、D6、D7
- ..。
いずれの場合も、クリアされたSAITaskClassLoaderにはGCルートがありません。OpenJPA1.2.1を使用しています。
感謝をこめて