現場から学ぶWebアプリ開発のトラブルハック 第9回

JavaのGC頻度に惑わされた
年末年始の苦いメモリ


株式会社NTTデータ 基盤システム事業本部
茂呂 範
2007/12/27


【第5話】犯人はお前だ! Finalizer!!

 先ほどの4つのGCグラフを比較すると、明らかにFull GC(GCグラフ内の黒い縦線)の出方に違いがあることが分かった。

 「Finalizerの仕業か!」

 同僚の言葉に、私もうなずかざるを得なかった。

Finalizerとは何者か?

 さてここで、簡単にFinalizerについて説明しよう。Finalizerは、Javaオブジェクトのfinalizeメソッドを呼び出すスレッドだ。

 通常、ルートセットからたどれなくなったオブジェクトは、GCの対象となる。このとき、finailizeメソッドが定義されていなければ、そのままJavaヒープ上からは削除される。しかし、finalizeメソッドが定義されていると、一度finalize対象リストへと格納され、すぐにはJavaヒープ上からは削除されない。

 削除されるタイミングは、Finalizerスレッドによりfinalizeメソッドが呼び出された後の、GCのときだ。

finalizeメソッドがないときのJavaオブジェクト

 次の図8は、通常のJavaオブジェクト、すなわちfinalizeメソッドが定義されていないJavaオブジェクトの場合のライフサイクルを表す。

図8 通常のJavaオブジェクトのGCの様子
図8 通常のJavaオブジェクトのGCの様子
  1. オブジェクトがアプリケーションを実行するために利用されている間は、スレッドルートセット)から参照されるため、GC対象とはならない
  2. オブジェクトが不必要となったとき、すなわちスレッド(ルートセット)からの参照が外されるとき、そのオブジェクトはGC対象となる
  3. GCが実行されるときに、オブジェクトはJavaヒープ上から完全に削除される

 では、finalizeメソッドが定義されたオブジェクトはどうだろうか。

finalizeメソッドがあるときのJavaオブジェクト

 次の図9は、finalizeメソッドが定義されたJavaオブジェクトのライフサイクルを表す。

図9 finalizeメソッドが定義されたJavaオブジェクトのGCの様子
図9 finalizeメソッドが定義されたJavaオブジェクトのGCの様子
  1. オブジェクトがアプリケーションを実行するために利用されている間は、ルートセットから参照されるため、GC対象とはならない
  2. オブジェクトが不必要となったとき、すなわちルートセットからの参照が外されるとき、そのオブジェクトはGC対象となる
  3. GCが実行されるときに、オブジェクトはfinalize対象リストへと登録され、ルートセットから参照されることとなる
  4. Finalizerスレッドが、オブジェクトのfinalizeメソッドを実行し、再度ルートセットからの参照が外され、GC対象となる
  5. GCが実行されるとき、今度こそオブジェクトはJavaヒープ上から完全に削除される

 このように、finalizeメソッドが定義されたJavaオブジェクトがJavaヒープ上から削除されるためには、少なくとも2回以上のGCが必要だということが分かるだろう。

少なくとも2回以上のGCが必要なのに……

 先ほどの4つのGCグラフをもう一度見てほしい。メモリリークと思われていた、すなわちヒープ使用量が上昇していたGCグラフ(図2)は、2回以上Full GCが発生していない。

 また、ヒープ使用量が上昇していないGCグラフは、かなりの頻度でFull GCが発生していることが分かるだろう。

【第6話】GCのやっていることは全部お見通しだ!

 Finalizerが怪しいところまでは突き止めた。後は再現試験を実施するだけだ。再び同じように検証を行って、2度目のFull GCが発生したときに、ヒープサイズ大きく減少していればFinalizerの仕様が原因であるといえるだろう。

 早速、試験を行った。今回の試験は、Full GCの発生を促すために、最大ヒープサイズを非常に小さく設定して行った。

図10 GCグラフ:Finalizer再現試験
図10 GCグラフ:Finalizer再現試験

 1度目のFull GC(グラフ内では黒い縦線)では、オブジェクトはそれほど回収されておらず、2度目のFull GCで大きく回収されていることが分かる。そしてその幅が徐々に小さくなっていることが分かる。

 この再現試験の結果をもって、われわれの役目は終わった。メモリリークではない、Full GCの発生頻度を上げればよい、というところまで分かれば、後はプロジェクトメンバがどのような対策を実施するかを決定するだけだ。

 帰って雑煮でも食べることにしよう……

Full GC頻度の「少なさ」によるトリック

 今回は、Full GCの頻度が少なかったために発生したトラブルを紹介した。GCによるトラブルというと、Full GCの実行時間が長過ぎる、Full GCの頻度が多過ぎる、などが挙げられると思う。

 そのため、Full GCの「多さ」や「長さ」を気にすることはあっても、「少なさ」を気にすることは少ないのではないだろうか。しかし、今回の事例のように、Full GCの頻度が少な過ぎるために発生するトラブルもあることを知っていただければと思う。

【注意!】Finalizerが引き起こす3つのトラブル

 Finalizerに関する挙動は、Javaの中でも厄介な部類に入るだろう。今回は事例として紹介しなかったが、Finalizerに関しては、次のようなトラブルも発生する。簡単に紹介しよう。

【1】Finalizerメソッドの処理が重いことによるOutOfMemoryError

 Finalizerスレッドによる後処理のスピードがGCのスピードに追い付かず、finalize待ちオブジェクトがいつまでたっても解放されないために、メモリを圧迫するようになりOutOfMemoryErrorが発生する。

【2】Finalizerスレッド・デッドロックによるOutOfMemoryError

 Finalizerスレッドがデッドロックにより停止したため、finalize待ちオブジェクトが開放されず、メモリを圧迫するようになりOutOfMemoryErrorが発生する。

【3】ヒープサイズのフットプリントの上昇(厳密にはトラブルでない)

 finalizeの対象となるオブジェクトが多いと、それだけFull GC時に回収されるオブジェクトが少なくなる。これは、ヒープサイズのフットプリントを大きくし、プログラム動作により多くのメモリ量を必要とすることになる。

【最後に】「先入観」「思い込み」「事実と推測を混同」

 このように、Finalizerはメモリ回りのトラブルを引き起こしやすい、厄介な存在だ。だからといってFinalizerに頼るような設計を行うべきではない、というのは簡単だが、現実的には標準API内でも多用されている以上、Finalizerに頼らずにソフトウェアを構築することは難しい。

 やはり、Javaを利用している以上、GCの動作には常に注意を払っておき、それをもって、トラブルを未然に防ぐための予防としたい。

 ところでお気付きかもしれないが、本稿は、トラブル解析の際にちょっとだけ失敗した事例の紹介だ。どこで失敗したかはご想像にお任せする。

 キーワードは、「先入観」「思い込み」「事実と推測を混同」だ。これらのキーワードに引っかかったままトラブルハックを行うと、痛い目に遭うということを思い知らされる苦い思い出だが、良い事例だった。

@IT関連記事


実行速度に挑戦してきたJava VMの歴史
Javaの歴史は実行速度向上の歴史でもあった。今日のJava VMが完成するまでのアーキテクチャの変遷を振り返ることで、Java VMの理解をより深めることができる
Java Solution」フォーラム 2003/12/11
HotSpot VMの基本構造を理解する
チューニングのためのJava VM講座(前編) パフォーマンスチューニングに関わるエンジニアのためのJava VM講座。2回に渡ってHotSpot VMの基礎知識を解説します
Java Solution」フォーラム 2004/3/11
事例に学ぶWebシステム開発のワンポイント
現場のエンジニアの経験から得られた、アプリケーション・サーバをベースとしたWebシステム開発におけるノウハウ、注意点について解説
第1回 クラスタ化すると遅くなる?(2002/3/9)
第2回 キャッシュが性能劣化をもたらす謎を解く (2002/3/23)
第3回 クラスタは何台までOK? (2002/4/19)
第4回 マルチスレッドのいたずらに注意 (2002/5/14)
第5回 クラスタによるアプリケーションの動的アップデート (2002/6/4)
第6回 APサーバからの応答がなくなった、なぜ? (2002/11/30)
第7回 低負荷なのにCPU使用率が100%?
(2002/12/11
第8回 文字化け“???”の法則とその防止策 (2003/1/28)
第9回 メモリは足りているのに“OutOfMemory”のなぞ (2003/2/15)
第10回 レスポンスキャッシュでパフォーマンス向上 (2003/3/29)
第11回 JDBC接続を高速化−PreparedCacheの活用 (2003/4/18)
第12回 ブラウザキャッシュでパフォーマンス向上 (2003/5/10)
第13回 ファイルアップロード/ダウンロードに潜むわな2003/6/12)
連載各回の解説はこちら

1-2-3

Index
第9回 JavaのGC頻度に惑わされた年末年始の苦いメモリ
  Page1
【第1話】「大みそかだというのに……」
【第2話】“現場”での聞き込みが大事
  Page2
【第3話】伝家の宝刀にご登場願おう
【第4話】「再現していません!!」
Page3
【第5話】犯人はお前だ! Finalizer!!
【第6話】GCのやっていることは全部お見通しだ!
【注意!】Finalizerが引き起こす3つのトラブル
【最後に】「先入観」「思い込み」「事実と推測を混同」

プロフィール
茂呂 範(もろ すすむ)

株式会社NTTデータ 基盤システム事業本部所属。入社時よりOSSを用いたWebシステムの開発支援にかかわる。最近では、トラブルシューティングとその際のノウハウの収集・展開に日々従事している。



Java Solution全記事一覧


TechTargetジャパン

Java Solution フォーラム 新着記事

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH