クラスローダリークとヒープダンプ

Java EE を使っている人なら多くの人が遭遇するクラスローダリーク。EclipseなどのIDEホットデプロイしながら開発していると、突然APサーバから応答がなくなったり、java.lang.OutOfMemory : PermGenが出力されたりする。

原因についてはnekopさんのスライドyamadanさんのブログに紹介されているので、ここではクラスローダリークを手元で再現させてみて、どのようなヒープダンプになるか紹介する。

ヒープダンプの解析にはEclipseMemoryAnalyzer(MAT)を使う。

1. Duplicate Classes を確認する

クラスローダリークが発生している状況では、同一のクラスが異なるクラスローダよりロードされている事象が起こっている。MATでは重複クラスロードの一覧を表示する機能があるため、1クリックで確認できる。

f:id:n_agetsuma:20140517122757p:plain

Tomcatの場合

Tomcat(8.0.5)はWARごとにorg.apache.catalina.loader.WebappClassLoaderによって、WARファイル内に含まれるクラスをロードする。クラスローダリークが発生していると、同じクラスを持つWARは1つしかデプロイしていないはずなのに、複数のクラスローダによって読み込まれていることがわかる。

f:id:n_agetsuma:20140517123044p:plain

WildFlyの場合

WildFly8.1.0CR2で試しても同様にorg.jboss.modules.ModuleClassLoaderが重複ロードしていることがわかる。

f:id:n_agetsuma:20140517123356p:plain

2. Paths to GC Roots の確認

ここまででクラスローダリークかどうかを識別したが、原因がわからない。何が原因でGCされていないかは、対象のクラスローダインスタンスを選択して右クリックし、Path to GC Rootsを確認する。

f:id:n_agetsuma:20140517123917p:plain

以下のように、TomcatのプールされているTaskThreadが持つThreadLocalから参照されてGCされないことがわかる。

f:id:n_agetsuma:20140517124019p:plain

WildFly8でも同様に、プールされているスレッドのThreadLocalから参照されてGCされないことがわかる。

f:id:n_agetsuma:20140517124331p:plain

ThreadLocalにユーザ情報などリクエスト共通で使いたいデータを格納した後、ThreadLocal.remove()を実行せずにAPサーバのプールにスレッドを返す、よくあるクラスローダリークの原因例を示したヒープダンプでした。ServletFilterなどを使ってタスクスレッドがAPサーバのプールに返る前にThreadLocal.remove()で掃除することが大切です。

追記

第十回 渋谷Javaでクラスローダリークについてお話しました: