WebObjects 4: Java アプリケーションのメモリリーク
対象製品
WebObjects
「WebObjects Java アプリケーション」には、2 つの一般的なタイプのメモリリークがあります。Java の garbage collection スキームによって生じるリークと、“Java”、または、“WebObjects Java コンポーネント”に起因した問題を原因とするリークです。この記事では、WebObjects 4 を使用していることを前提に、この 2 つのタイプのメモリリークを防ぎ、解析する方法を説明します。
garbage collection に起因するリーク
garbage collector は、使われていないオブジェクトを見つけだし、解放するために繰り返し実行しなければならない場合がたびたびあります。Sun 社製 garbage collector は、garbage がまだ残っていても収集をやめてしまうことがあります。Java ブリッジを巡るオブジェクトリファレンスは、クリーンアップされる前にコレクターの多数の通過を要求することができます。Java garbage collector が“Objective C”オブジェクトによって作られたリファレンスを参照できないため、“Objective C”オブジェクトで作られたリファレンスは「非コレクタブルリファレンス」ではなく「グローバルリファレンス」として現れます。つまり、いくつかのコレクションは、最初は Java garbage collector、次に“Objective C”garbage collector、また再度“Java”garbage collector といった具合に、関係を分散して処理することを要求されることがあります。この結果、アプリケーションからメモリの一時的なリークが生じてしまいます。
たとえば、“Objective C”オブジェクトが“Java Garbage Collector”スレッドによって収集された Java 側の garbage を持っている場合、“Objective C”オブジェクトはすぐには割り当ての取り消しはされません。これは、メインスレッドが Java ブリッジを通過したときのみ起こります。ブリッジがグローバルな保持カウントを解放したとき、ブリッジは“Objective C”オブジェクトに対し「Java VM」の利益を保ち、より多くの“Java / Objective C”オブジェクトを解放します。安定した状態にするには、ユーザは強制的にコレクションのループとオートリリースを用いて、複数の garbage collector を実行させる必要があります。WebObjects 4 では、セッションの割り当てを取り消している間、garbage collector に収集をループさせるための 2 つの異なった環境変数、WOGarbageCollectionRepeatCount と WOWorkerThreadCount を設定することにより、これを実行することができます。
WOGarbageCollectionRepeatCount は、タイムアウトしたセッションによって保持されるほとんどのオブジェクトが解放されるのに十分な回数だけ、garbage collection を強制的に発生させるために用いられます。WOGarbageCollectionRepeatCount の値として「3」を設定すると、WebObjects は、ユーザが(平均で)およそ「3」の参照する深さのオブジェクトグラフを持っていることを意味します。これはまた、ユーザデフォルトが特定されていない場合の「デフォルト値」でもあります。「WOGarbageCollectionRepeatCount == 0」と設定すると、セッションの割り当てが取り消されることはありません。
WOWorkerThreadCount は、マルチスレッドアプリケーションに対するワーカースレッドの最大数を特定します。WOWorkerThreadCount のデフォルト値は、「8」に設定されています。この設定を「0」にすると、WebObjects 3.5.1 と同様、結果的にシングルスレッドの要求至急処理となります。「WOWorkerThreadCount value == 0」と設定した場合、WOGarbageCollectionRepeatCount はなんの効果ももたらさなくなる点に注意してください。
Project Builder では、“Launch Panel”を引き出し、以下のように起動変数を追加します。
-WOWorkerThreadCount 8 -WOGarbageCollectionRepeatCount 6
WOWorkerThreadCount は「0」以上の値、WOGarbageCollectionRepeatCount は「3」から「8」までの値を用いることができます。またコマンドラインからアプリケーションを起動する際に、これら 2 つの変数を指定することも可能です。ただし、garbage collector を繰り返し実行することは、お使いのアプリケーションのパフォーマンスに影響を与えることに注意してください。
すでに知られている Java メモリ問題に起因するリーク
固定の、またはグローバルなレファレンス
リークは、使用しているアプリケーションが割り振られたセッションの配列や、ある種のグローバル通知レジストリを保持しようとするときに起こる場合があります。Java (JDK 1.1) が「weak references」をサポートしていないため、その配列に置かれたオブジェクト、配列によって参照されるすべてのオブジェクトは収集されません。JDK 1.2 でも、この問題を回避するために「weak references」を使うことは、なお疑問が残ります。
Java ブリッジを越えてサイクルを保持する
リークの原因として可能性のある源は、アプリケーションがブリッジを越えたリファレンスサイクルを生成したときです。たとえば、WOApplication は“Objective C”を、WOSessions 上に留まらせます。そこで WOSession の“Java サブクラス”が「WOApp」を指すインスタンス変数を追加した場合、ブリッジを越えるサイクルが作られ、そのセッションは収集されません。
この問題は、以下の WebObjects 4 リリースノートに報告されています。
Apple Reference # 2282368
問題:Java の“Inner クラス”がサイクルの留保をもたらし、その結果、セッションとページがリークされる。
説明:セッション、あるいはページ内(または、どちらか一方)でインナークラスを使用している場合は、静的でないインナークラスに所有するクラスを示すポインタが隠れていることに注意してください。これは、“Objective C”と“Java”の混在したサイクルが簡単に作られ、このサイクル上ですべてのオブジェクトの割り当ての取り消しが妨げられるという状況を引き起こします。たとえば、サブコンポーネントの 1 つへインナークラスのインスタンスを通過させると、以下のようなサイクルが生じます。
yourSubcomponent > theInnerClassInstance > (隠れたインスタンス変数を通じて) theTopLevelPage > (通常の“objC”の抑制を通じて)> yourSubComponent
解決策:サイクルを止めるには、隠れたインスタンス変数を持たない、「静的な」インナークラスを使う方法があります。