先に述べたように、GCはCopy GCとFull GCとを組み合わせて行われる。Copy GCは対象がNew領域のみであるため、短時間に処理が終了する。しかし、Full GCではJavaヒープ全体とPermヒープを対象とするためGCの処理時間が長くなる。
GCの処理中は、JavaVMはユーザーのアプリケーションを停止する。そのため、Full GCが多発するとシステムの性能面への影響が大きい。安定したWebシステムを構築するためには、「Full GCの発生を抑えること」、そしてその上で「Copy GCの発生頻度をコントロールすること」を目指す必要がある。
そのため、メモリサイジングを行う場合には、さまざまなバランスを考えて設定する必要がある。基本的な考え方は以下のとおりである。
Copy GCの発生間隔が想定に入るようにサイズを見積もる必要がある。例えばWebシステムでは、毎秒のリクエスト数、リクエストで使用するメモリサイズとCopy GCの発生間隔からEden領域のサイズを見積もることができる。
生存区間の長いオブジェクトのサイズを元に見積もることになる。例えばWebシステムでは、セッションのサイズや毎秒のログイン数、Full GCの許容間隔などの情報からOld領域のサイズは見積もることになる。加えて、Copy GCであふれたときのためのNew領域の退避用領域も必要となる。
生存区間が長いオブジェクトがどのくらいあるのかを考慮し、それらの扱いを決める必要がある。メモリサイズを大きくしたり、Old領域への昇格を遅くしたりすると、Copy GCの対象が増えて性能面に影響する。逆に、メモリサイズを小さくしたり、Old領域への昇格を早めたりすると、若い世代のオブジェクトがOld領域に流入してしまい、Full GCの機会が増加する。
このように、先に紹介したオプションでメモリ空間のサイズや割合を適切に設定することが重要になる。
Javaヒープのサイズは適切に見積もったはずなのに、予想外のFull GCに見舞われるといった経験はないだろうか。見落としがちなFull GCの発生要因として、Permヒープの枯渇がある。Permヒープが枯渇している場合にもFull GCは発生するので注意が必要だ。
Permヒープが枯渇していないかを確認するには、JavaVMのオプションに「-verbose:gc」及び「-XX:+PrintGCDetails」オプションを指定するとよい。これらの設定を行うと、JavaVMのログにGC発生前後での各領域の使用メモリサイズが出力される。
なお日立のCosminexusの場合には、拡張verbosegc情報を出力するオプション(-XX:+HitachiVerboseGCオプション)が用意されている。このオプションを指定すると、JavaVMのログ情報にGC実行前後の各領域の使用メモリサイズに加えて、GCの発生した要因を出力する。例えば、この場合には、以下に示すようにJavaVMのログにそれぞれの領域の「<GC前の領域長>-><GC後の領域長>(<領域サイズ>)」の情報とGCの発生要因やGCスレッドのCPU時間などが次のように出力される。
[VGC][Full GC 57051K->25121K(129792K), 0.5531230 secs][DefNew::Eden: 40943K->0K(41088K)][DefNew::Survivor: 1280K->0K(1280K)][Tenured: 14827K->25121K(87424K)][Perm: 20479K->20479K(20480K)][cause:PermAllocFail][User: 0.0156250 secs][Sys: 0.0312500 secs]
[Perm: 20479K->20479K(20480K)][cause:PermAllocFail]となっている箇所が、GC発生原因の情報である。Permヒープ領域が枯渇していることが、ひと目で特定できる。Permヒープ領域のサイズは、デフォルトでは小さい傾向があるため、大規模な構成の場合は、パラメータで大きくするのを忘れてはならない。
ITアーキテクトに求められる「Javaの互換性やサポート」という視点Copyright© 2012 ITmedia, Inc. All Rights Reserved.