現場から学ぶ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の様子 |
- オブジェクトがアプリケーションを実行するために利用されている間は、スレッド(ルートセット)から参照されるため、GC対象とはならない
- オブジェクトが不必要となったとき、すなわちスレッド(ルートセット)からの参照が外されるとき、そのオブジェクトはGC対象となる
- GCが実行されるときに、オブジェクトはJavaヒープ上から完全に削除される
では、finalizeメソッドが定義されたオブジェクトはどうだろうか。
■ finalizeメソッドがあるときのJavaオブジェクト
次の図9は、finalizeメソッドが定義されたJavaオブジェクトのライフサイクルを表す。
![]() |
| 図9 finalizeメソッドが定義されたJavaオブジェクトのGCの様子 |
- オブジェクトがアプリケーションを実行するために利用されている間は、ルートセットから参照されるため、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の発生を促すために、最大ヒープサイズを非常に小さく設定して行った。
![]() |
| 図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) 連載各回の解説はこちら |
||
| Index | |
| 第9回 JavaのGC頻度に惑わされた年末年始の苦いメモリ | |
| Page1 【第1話】「大みそかだというのに……」 【第2話】“現場”での聞き込みが大事 |
|
| Page2 【第3話】伝家の宝刀にご登場願おう 【第4話】「再現していません!!」 |
|
| Page3 【第5話】犯人はお前だ! Finalizer!! 【第6話】GCのやっていることは全部お見通しだ! 【注意!】Finalizerが引き起こす3つのトラブル 【最後に】「先入観」「思い込み」「事実と推測を混同」 |
|
| プロフィール |
| 茂呂 範(もろ すすむ) 株式会社NTTデータ 基盤システム事業本部所属。入社時よりOSSを用いたWebシステムの開発支援にかかわる。最近では、トラブルシューティングとその際のノウハウの収集・展開に日々従事している。 |
現場から学ぶWebアプリ開発のトラブルハック バックナンバー 連載インデックスへ»
- 第1回 Webアプリの問題点を「見える化」する7つ道具
- 第2回 “Stop the World”を防ぐコンカレントGCとは?
- 第3回 【実録ドキュメント】そのログ本当に必要ですか?
- 第4回 DBアクセスのトラブルは終盤で発覚しがち……
- 第5回 OutOfMemoryエラー発生!? GCがあるのに、なぜ?
- 第6回 【真夏の夜のミステリー】Tomcatを殺したのは誰だ?
- 第7回 【トラブル大捜査線】失われたコネクションを追え!
- 第8回 肥え続けるTomcatと胃を痛めるトラブルハッカー
- 第9回 JavaのGC頻度に惑わされた年末年始の苦いメモリ
- 第10回 ThreadとHashMapに潜む無限回廊は実に面白い?
- 第11回 スレッドダンプの森で覚えた死のロックへの違和感
- 第12回 アプリ開発でも、よ〜く考えよう。キャッシュは大事だよ
- 第13回 DB操作の“壁”を壊すJPAが起こした「赤壁の戦い」
- 第14回 数百キロのコードでブルー - ドクターTomcat緊急救命
| Java Solution全記事一覧 |
TechTargetジャパン
- Scalaのパッケージ、アクセス修飾子、オブジェクト継承 (2012/5/22)
インポート、パッケージオブジェクト、抽象クラス/抽象メソッド、オーバーライド、final、シールドクラスなども - 基幹系システムでCloud SQLは使えるか試してみた (2012/5/17)
サンプルとしてMRPシステムを作成して動かし、「再帰呼び出し」などのパフォーマンスを測定して検証してみます - アジャイル管理ツール9選+Pivotal Tracker入門 (2012/5/14)
群雄割拠のアジャイルプロジェクト管理ツールを9つ紹介し、特に注目を集めているPivotal Trackerの基本的な使い方を解説します - サーバサイドJSやJavaでWebアプリが作れるXPages (2012/5/11)
Notes/Dominoの資産をサーバサイドJavaScriptやJavaで操作し、HTMLやJavaScript、CSSをUIにできる技術を紹介
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -



