先ほどの4つのGCグラフを比較すると、明らかにFull GC(GCグラフ内の黒い縦線)の出方に違いがあることが分かった。
「Finalizerの仕業か!」
同僚の言葉に、私もうなずかざるを得なかった。
さてここで、簡単にFinalizerについて説明しよう。Finalizerは、Javaオブジェクトのfinalizeメソッドを呼び出すスレッドだ。
通常、ルートセットからたどれなくなったオブジェクトは、GCの対象となる。このとき、finailizeメソッドが定義されていなければ、そのままJavaヒープ上からは削除される。しかし、finalizeメソッドが定義されていると、一度finalize対象リストへと格納され、すぐにはJavaヒープ上からは削除されない。
削除されるタイミングは、Finalizerスレッドによりfinalizeメソッドが呼び出された後の、GCのときだ。
次の図8は、通常のJavaオブジェクト、すなわちfinalizeメソッドが定義されていないJavaオブジェクトの場合のライフサイクルを表す。
では、finalizeメソッドが定義されたオブジェクトはどうだろうか。
次の図9は、finalizeメソッドが定義されたJavaオブジェクトのライフサイクルを表す。
このように、finalizeメソッドが定義されたJavaオブジェクトがJavaヒープ上から削除されるためには、少なくとも2回以上のGCが必要だということが分かるだろう。
先ほどの4つのGCグラフをもう一度見てほしい。メモリリークと思われていた、すなわちヒープ使用量が上昇していたGCグラフ(図2)は、2回以上Full GCが発生していない。
また、ヒープ使用量が上昇していないGCグラフは、かなりの頻度でFull GCが発生していることが分かるだろう。
Finalizerが怪しいところまでは突き止めた。後は再現試験を実施するだけだ。再び同じように検証を行って、2度目のFull GCが発生したときに、ヒープサイズ大きく減少していればFinalizerの仕様が原因であるといえるだろう。
早速、試験を行った。今回の試験は、Full GCの発生を促すために、最大ヒープサイズを非常に小さく設定して行った。
1度目のFull GC(グラフ内では黒い縦線)では、オブジェクトはそれほど回収されておらず、2度目のFull GCで大きく回収されていることが分かる。そしてその幅が徐々に小さくなっていることが分かる。
この再現試験の結果をもって、われわれの役目は終わった。メモリリークではない、Full GCの発生頻度を上げればよい、というところまで分かれば、後はプロジェクトメンバがどのような対策を実施するかを決定するだけだ。
帰って雑煮でも食べることにしよう……
今回は、Full GCの頻度が少なかったために発生したトラブルを紹介した。GCによるトラブルというと、Full GCの実行時間が長過ぎる、Full GCの頻度が多過ぎる、などが挙げられると思う。
そのため、Full GCの「多さ」や「長さ」を気にすることはあっても、「少なさ」を気にすることは少ないのではないだろうか。しかし、今回の事例のように、Full GCの頻度が少な過ぎるために発生するトラブルもあることを知っていただければと思う。
Finalizerに関する挙動は、Javaの中でも厄介な部類に入るだろう。今回は事例として紹介しなかったが、Finalizerに関しては、次のようなトラブルも発生する。簡単に紹介しよう。
Finalizerスレッドによる後処理のスピードがGCのスピードに追い付かず、finalize待ちオブジェクトがいつまでたっても解放されないために、メモリを圧迫するようになりOutOfMemoryErrorが発生する。
Finalizerスレッドがデッドロックにより停止したため、finalize待ちオブジェクトが開放されず、メモリを圧迫するようになりOutOfMemoryErrorが発生する。
finalizeの対象となるオブジェクトが多いと、それだけFull GC時に回収されるオブジェクトが少なくなる。これは、ヒープサイズのフットプリントを大きくし、プログラム動作により多くのメモリ量を必要とすることになる。
このように、Finalizerはメモリ回りのトラブルを引き起こしやすい、厄介な存在だ。だからといってFinalizerに頼るような設計を行うべきではない、というのは簡単だが、現実的には標準API内でも多用されている以上、Finalizerに頼らずにソフトウェアを構築することは難しい。
やはり、Javaを利用している以上、GCの動作には常に注意を払っておき、それをもって、トラブルを未然に防ぐための予防としたい。
ところでお気付きかもしれないが、本稿は、トラブル解析の際にちょっとだけ失敗した事例の紹介だ。どこで失敗したかはご想像にお任せする。
キーワードは、「先入観」「思い込み」「事実と推測を混同」だ。これらのキーワードに引っかかったままトラブルハックを行うと、痛い目に遭うということを思い知らされる苦い思い出だが、良い事例だった。
茂呂 範(もろ すすむ)
株式会社NTTデータ 基盤システム事業本部所属。入社時よりOSSを用いたWebシステムの開発支援にかかわる。最近では、トラブルシューティングとその際のノウハウの収集・展開に日々従事している。
Copyright © ITmedia, Inc. All Rights Reserved.