[Seasar-user:5928] Re: [S2Container]S2FrameworkTestCase/UnitClassLoaderの実装について

kubo [E-MAIL ADDRESS DELETED]
2007年 1月 27日 (土) 21:12:11 JST


久保です。

> > システムクラスローダーからロードされたクラス(A)の
> > インスタンス(a)が,エンハンスされたクラス(B)の
> > インスタンス(b)を参照する場合,インスタンス(a)が
> > GC されるのであれば問題ありません.
> > 
> > しかし,クラス(A)の static フィールドから
> > インスタンス(b)を参照する場合は,クラス(A)は
> > アンロードされないため,static フィールドが明示的に
> > クリアされない限りインスタンス(b)も GC されず,
> > クラス(B)もアンロードされません.
> > 結果,UnitClassLoader も GC されません.
> 
> 理解しました。
> 後者のPattern(&その他の原因等)を探してみます。


他で同様の現象があった場合に参考になるように「その後報告」いたします。


以前のメールにて

1. まず、OutOfMemory - PermGen  が発生
2. 大量SetterInjectionを止めたらOutOfMemory - JavaHeap が発生

を報告しましたが、こちらは「本当の原因」にPlusして「付属した原因」のために
発生していたものでした。

付属した原因とは、DiconのIncludeをContainerのInitの後に実行してしまっていたため、
IncludeされたDiconの方はDestory時に inited:false のためDestoryがされていませんでした。
なので、JavaHeapが圧迫してOutOfMemoryになってしまっていました。
(Initしていなくても正常に動作していたことにちょっと驚き...)

そして、それを直したところ、再度 PermGen のOutOfMemoryが発生しました。
小林さんが指摘されていた「EnhanceされたClassのInstanceを誰かがstaticで保持」
を徹底して探したのですが見つからず。他の原因もひたすら追っかけたのですが見つからず。
途方にくれつつも、環境の違いを一つ一つ精査していって「起きる場合と起きない場合」の
境目を発見いたしました。これが「本当の原因」となります。


<結論>
結論から言いますと、

A where MySQLに対してJDBC(MySQL-Connector-3.1.x or 5.0.x)でAccess
B   and Interceptor内で物理的なConnectionの生成を行う(ConnectionPoolへのAccess一回目)
C   and Interceptor内でそのConnectionのclose()を呼び出す(ConnectionPoolへ返却)

の条件でUnitClassLoaderが破棄されないことがわかりました。
(jconsleにてClassのLoading数がひたすら増加するのでそのように判断)


<補足>
「A」に関して、SQLServerでは発生しません。

「A」に関して、ApacheDerbyでは発生しません。

「B」に関して、Interceptor内でも論理的なConnectionの取得でれば発生しません。

「B」がInterceptor外であれば発生しません。
  →close()がどこであろうと同じ

「C」をしなければ発生しません。
  →ConnectionPoolのDestroy時はactivePoolに入った状態で破棄される。

「C」がInterceptor外であれば発生しません。
  →static変数経由で呼び出しMethodへ戻してそこでclose()など

「Interceptor内」というのはInterceptor-Classがnewして呼び出してClassも同様。
Interceptorされた本当のClassではinvoke.proceed()の先は「Interceptor外」


<考察>
厳密には原因はわかっておりません。Interceptorの世界でMySQLの物理的なConnectionを
生成するとNGなのかなと思いつつも、close()を呼ばなければOKというのがわかりません。
(close()は単にConnectionPoolに返してるだけですし...)

そして、上記の条件は、MySQL+S2Daoで簡単に発生します。
なぜなら、S2DaoのDaoの処理はInterceptor内で行われているからです。
まず最初にDaoの初期化が実行され、そのときDBのMeta情報を取得するためにConnectionを
取得しにいきます。そこが発生する・しないの境目であることがわかりました。
(OpenSourceで本当に良かったぁ...Source書き換えまくって調べました)


もっと厳密なテストをしたいのですが(InterceptorでConnectionPool使わずにConnection生成とか)、
上記の検証だけで随分時間を使ってしまいこれ以上は業務に支障がでてしまうため、
こちらとしては調査を打ち切りました。

そして、ある回避策を導入しました。

  物理的なConnectionの生成を「Interceptor外」で行い
  Interceptor内のConnection取得は全て「論理的なConnectionの取得」にする。

つまり、ContainerのInit後Test実行前のTimingで、無意味にConnection取得&close()するようにしました。
すると、その時点で物理的なConnectionの生成を行い、TestMethodで利用するConnectionは
全て「既にConnectionPoolに存在するものを再利用」します。(TestはSingleThreadなので)

これで、OutOfMemoryが発生しなくなりました。jconsoleのClassのLoading数は安定。


Javassist/S2Container/MySQLのJDBC-Driver と絡み合っているので原因がとてもつかみ辛いです。
他のDBでは全く問題が無いので、MySQLのJDBC-Driverが一番怪しいですが、
そこを追っかけていくのはあまりにもCostが大きそうです。
なので、「回避」で先に進むことにします。




上記内容をここで報告させて頂きます。



-- 
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
kubo   <[E-MAIL ADDRESS DELETED]>
jflute <http://d.hatena.ne.jp/jflute>
株式会社ビルドシステム <http://www.buildsystem.co.jp>
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/





Seasar-user メーリングリストの案内