JavaのGC頻度に惑わされた年末年始の苦いメモリ:現場から学ぶWebアプリ開発のトラブルハック(9)(3/3 ページ)
本連載は、現場でのエンジニアの経験から得られた、APサーバをベースとしたWebアプリ開発における注意点やノウハウについて解説するハック集である。現在起きているトラブルの解決や、今後の開発の参考として大いに活用していただきたい。(編集部)
【第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オブジェクトの場合のライフサイクルを表す。
- オブジェクトがアプリケーションを実行するために利用されている間は、スレッド(ルートセット)から参照されるため、GC対象とはならない
- オブジェクトが不必要となったとき、すなわちスレッド(ルートセット)からの参照が外されるとき、そのオブジェクトはGC対象となる
- GCが実行されるときに、オブジェクトはJavaヒープ上から完全に削除される
では、finalizeメソッドが定義されたオブジェクトはどうだろうか。
■finalizeメソッドがあるときのJavaオブジェクト
次の図9は、finalizeメソッドが定義されたJavaオブジェクトのライフサイクルを表す。
- オブジェクトがアプリケーションを実行するために利用されている間は、ルートセットから参照されるため、GC対象とはならない
- オブジェクトが不必要となったとき、すなわちルートセットからの参照が外されるとき、そのオブジェクトはGC対象となる
- GCが実行されるときに、オブジェクトはfinalize対象リストへと登録され、ルートセットから参照されることとなる
- Finalizerスレッドが、オブジェクトのfinalizeメソッドを実行し、再度ルートセットからの参照が外され、GC対象となる
- 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の発生を促すために、最大ヒープサイズを非常に小さく設定して行った。
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の動作には常に注意を払っておき、それをもって、トラブルを未然に防ぐための予防としたい。
ところでお気付きかもしれないが、本稿は、トラブル解析の際にちょっとだけ失敗した事例の紹介だ。どこで失敗したかはご想像にお任せする。
キーワードは、「先入観」「思い込み」「事実と推測を混同」だ。これらのキーワードに引っかかったままトラブルハックを行うと、痛い目に遭うということを思い知らされる苦い思い出だが、良い事例だった。
プロフィール
茂呂 範(もろ すすむ)
株式会社NTTデータ 基盤システム事業本部所属。入社時よりOSSを用いたWebシステムの開発支援にかかわる。最近では、トラブルシューティングとその際のノウハウの収集・展開に日々従事している。
- 数百キロのコードでブルー - ドクターTomcat緊急救命
- DB操作の“壁”を壊すJPAが起こした「赤壁の戦い」
- アプリ開発でも、よ〜く考えよう。キャッシュは大事だよ
- スレッドダンプの森で覚えた死のロックへの違和感
- ThreadとHashMapに潜む無限回廊は実に面白い?
- JavaのGC頻度に惑わされた年末年始の苦いメモリ
- 肥え続けるTomcatと胃を痛めるトラブルハッカー
- 【トラブル大捜査線】失われたコネクションを追え!
- 【真夏の夜のミステリー】Tomcatを殺したのは誰だ?
- OutOfMemoryエラー発生!? GCがあるのに、なぜ?
- DBアクセスのトラブルは終盤で発覚しがち……
- 【実録ドキュメント】そのログ本当に必要ですか?
- “Stop the World”を防ぐコンカレントGCとは?
- Webアプリの問題点を「見える化」する7つ道具
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 実行速度に挑戦してきたJava VMの歴史
Javaの歴史は実行速度向上の歴史でもあった。今日のJava VMが完成するまでのアーキテクチャの変遷を振り返ることで、Java VMの理解をより深めることができる - HotSpot VMの基本構造を理解する
チューニングのためのJava VM講座(前編) パフォーマンスチューニングに関わるエンジニアのためのJava VM講座。2回に渡ってHotSpot VMの基礎知識を解説します - 事例に学ぶ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)