Javaは,メモリーの割り当てや解放などを担う「GC(ガベージ・コレクション)」と呼ばれるメモリー管理機構を備えている。GCによって煩雑なメモリー管理から開放されるという利点がある半面,GC実行時に処理停止が発生してしまうという特徴がある。

 GCの中でも,特定のメモリー領域がいっぱいになったときに実行されるのが「FullGC」である。FullGCは処理が重く,実行時には,メモリー内のオブジェクトの整合性を保持するため,アプリケーションスレッドをすべて停止する必要がある(この事象は「Stop The World」と呼ばれている)。この特徴はよく知られているにもかかわらず,プログラムの設計に反映されていないケースが意外と多い。

 I/O(入出力)待ちやネットワーク通信待ちは,なんらかの具体的な処理に付随して起こるためその待ち時間が意識される。一方,GCはどのタイミングで発生するか分からないため,設計の観点から漏れるのだろう。動き始めたアプリケーションスレッドは,常に動いているものと考えがちだが,これは危険だ。設計時には,GCによっていついかなるタイミングでも処理が停止する可能性がある,と考えておかなければならない。

待たされるのは人間だけではない

 GCを考慮しない設計によって生じる問題としてまず考えられるのが,オンライン・システムでのレスポンス・タイムの悪化である。ユーザビリティに直結するため,GCの弊害としてよく知られている。

 しかし,処理を待たされるのは何も人間だけではない。サーバー/クライアント方式で互いに通信しながら処理を実行するアプリケーションの場合,サーバー側がJavaで実装されていることで,GCにより通信処理がタイムアウトするといった事態も起こる。

 またアプリケーション内部に閉じた処理であっても,タイムアウトは起こり得る。システムタイムを基に待ち時間を設定しているような実装の場合,GCが終わった瞬間に大きく時間を飛び越えることになるため,即座にタイムアウトが発生する可能性がある。処理開始前の時刻と処理終了後の時刻の差分で処理時間を求めるような実装の場合,その結果が予想をはるかに超えた大きな値となることもある。

 Javaのアプリケーションで無応答状態が問題となった場合,問題の切り分けにも注意が必要となる。アプリケーション・スレッドが大忙しで高負荷状態となり応答できていないのか,それともGCにより全く動けていないのか。当然,いずれの状態かによって,対処方法が変わってくる。

GCを考慮してタイムアウトやリトライを設ける

 GCによるプログラム停止は常に想定して設計に盛り込むべきだ。設計時に,サーバー側でのGCの発生頻度とGC処理による停止時間の目標値を定めるようにし,クライアントはサーバー側GCで反応がなくなる時間帯があることを考慮して,タイムアウトやリトライを設けるようにする(図1)。

図1●GCによる停止時間を考慮した応答時間の見積もり
図1●GCによる停止時間を考慮した応答時間の見積もり

 実際に,目標としたGCによる停止時間をクリアするためには,FullGCの方式選択も重要となる。FullGCによる停止時間減少を目的としたGC方式も実装されている。「コンカレントGC」と呼ばれているもので,GC処理のうち時間のかかる処理をアプリケーション・スレッドと並列に実行することで,アプリケーションスレッドの停止時間を短縮できる。

 残念ながら,コンカレントGCはよい面ばかりではない。コンカレントGCを採用するとFullGC発生時の停止時間は短くなるが,スループットは低下する。簡単に言うと,従来のFullGCでは,ときどき(GC発生時)非常にレスポンスタイムが悪化するが,コンカレントGCではそのようなことがない変わりに,平均的なレスポンスタイムは悪くなるといえる。

 アプリケーションの特性が,比較的長い時間をかけ,その間の入力はあまりない状態で結果を出す,バッチ処理的なものである場合は,トータルの停止時間が短い従来のFullGCを利用し,短時間でインタラクティブに応答しなければならない要件の場合は,コンカレントGCを利用するなど,アプリケーション特性にあわせて最適なGC方法を選ぶ必要がある。

 また,いずれの方法もヒープ領域全体のサイズやヒープ内部の内訳比率をチューニングすることでGCによる停止時間を大きく削減できる可能性がある。

大上 貴充(おおがみ たかみつ)
NTTデータ 基盤システム事業本部 システム方式技術ビジネスユニット OSS技術統括 アソシエイトITスペシャリスト(プラットフォーム)
日頃はオリジナルOSSを中心に,技術開発やそのビジネス化に従事。OSをはじめ、Javaやクラスタミドルといった,アプリケーションの基礎となる技術分野を極めるべく,日夜奮闘中。