たぷつきません

おなかがでてきた。もうたぷついてるやん。

解放されないリフレクションクラス

 ある基幹システムを、Tomcat+Postgres+Tapestry+Seasar2+S2Tapestry+S2Daoの構成で開発したが、数日間連続稼動していると、パーマネント領域(以下Perm)がジワジワと減っていくという現象に悩まされている。Tenuredは十分ありGC/FullGC時の下限の平均は100MB前後に落ち着いている。にも関わらずPermは減るのだ。ヒストグラムを取ると判別可能クラス数は減っている時間帯でも、判別不可能クラス数は増加している。
 ヒストグラムで取得できるクラス名を便宜上、判別可能クラス、判別不可能クラスという言い方にしている。判別可能なのはクラス名称が明らかなもの。javaasistによりEnhancedされたものも、AOPアライアンスでEnhancedされたものも判別可能としている。これに対してリフレクションによる、sun.reflect.GeneratedConstructorAccessor+ユニークな番号、sun.reflect.GeneratedMethodAccessor+ユニークな番号というクラス名で定義されるものを判別不可能としている。
 最初はTapestryなどがポコポコEnhancedなページクラスやコンポーネントクラスを生成していることが原因ではないかと疑ったのだが全く問題なかった。一度作成したEnhancedなページクラス(およびその中に含まれる使用Enhancedなコンポーネントクラスも含む)は、リセットサービスが実行されない限り、一定のクラス名を保っているようだ。では増減した判別可能クラスを比較してコンストラクタの数やメソッドの数に大きな違いがあるのかと言えばそうではないようだった。
 起動してから3日後と4日後で比較した。時間帯によるものかたまたま4日後の方がロードされているページクラスが少なかった。ヒストグラムを取得した結果は以下だ。


3日後4日後
インスタンス数合計24405062387520
使用メモリ合計170369928148049312
クラス合計1181012454
GeneratedConstructorAccessor数:
695770
GeneratedMethodAccessor数:
80518720
 古くて未使用のリフレクションであれば、通常はFullGCなどで解放されるはずだ。Permからクラス数が減らないことで、Permの最大サイズ(デフォルト64MB)に達してしまい、末期には間断ないFullGCとOutOfMemoryの発生という大きな問題に悩まされることになる。javaの起動オプションに「-XX:MaxPermSize=128M」を入れてはいるが焼け石に水であることは言うまでもない。
 いろいろな可能性を探りながら調査しているが未だ原因が突き止められていない。深刻だ。ふと、実在クラスが解放されたあともリフレクションメソッドをキープしているものがいるのではないかという疑念が湧いた。構造上のメモリリークが起きているのではなかろうか。特にリフレクション呼び出しの固まりのようなOGNL辺りの挙動が気になる。
 運用上、稼動中にページやHTMLテンプレートの差し替えが1〜2日に1回程度の割合で起きる。そのためリセットサービスは実行されているのだが、この際にページクラスやコンポーネントなどは参照が消えて消えるクラスの対象となるが、OGNLの中ではメソッドアクセッサやコンストラクタアクセッサを一度持ってしまうとキャッシュしつづけるのではないか?
 手繰り寄せる糸が一本も無くなってしまって、この文書をつづり始めたのだが、この可能性を探ってみることにする。