Javaアプリケーションの性能を向上するためにGCの理解は不可欠
Javaでアプリケーションを開発する際にはGCを考慮した設計は必要不可欠である。特に、多数のユーザの利用が見込まれるコンシューマ向けのWebサービスなど、性能が重要視されるアプリケーションではGCの頻度が適切となるようなアプリケーション設計や、メモリサイズのチューニングが不可欠といえる。
本稿では、JavaにおけるGCの基本と設計、チューニングのポイントを紹介する。
GCの基本
Scavenge GCとFull GC
GCとは、Garbage Collection(ごみ収集)の略であり、メモリ上のごみオブジェクトを破棄するための機能である。
例えばC言語であればプログラム中で確保したメモリはソースコードで明示的に開放する必要があり、コーディングを忘れるとメモリリークの原因となってしまう。JavaではGCが存在するおかげでプログラマが明示的にメモリを開放しなくとも、メモリ上から自動的に不要なオブジェクトを破棄してくれるので、コーディングミスによるメモリリークの発生を防ぐことができる。
JavaのGCにはScavenge GCとFull GCの2種類が存在する。2つの違いを簡単に言うと、Scavenge GCが「日頃のお掃除」であるのに対し、Full GCは「年末の大掃除」のような違いがある。
「日頃の掃除」は頻度は高いが、よく使う部分のみでかかる時間も少ない。対して「年末の大掃除」は頻度は年に一回だが、広い範囲を長い時間をかけて行う。そして、掃除する対象はどちらもJVMのヒープ領域内である。次項では、より具体的な内容をみていこう。
参考記事:JVMのメモリ構造
Scavenge GCとFull GCの違いの一つはその対象範囲である。Scavenge GCはヒープ領域の中でもNew領域にあるオブジェクトのみを対象とするのに対して、Full GCではヒープ領域全体が処理対象となる。当然、その分だけ処理時間も長くなる。
図. ScavengeGCとFull GC対象範囲
次に、それぞれのGCの処理対象と処理内容を見ていこう。
[Scavenge GC]
Scavenge GCで処理対象となるのは、New領域上に存在するオブジェクトである。Scavenge GCはNew領域上のEden領域が一杯になったときに起動し、Eden領域とSurvivor領域上の未使用オブジェクトを破棄し、使用中オブジェクトをTo領域へと移動させる。
次回にGCが起動した場合にはFrom/To領域として扱われる領域が入れ替わるため、長い間使用中のままのオブジェクトがあると、GCのたびにSurvivor領域内をいったりきたりすることになる。そして、Survivor領域内を移動した回数が閾値(MaxTenuringThreshold)を超えると、Old領域に移動されることになる。
Old領域はScavenge GCでは処理対象とならないため、Full GCが稼動するまでは生き残り続けることになる。
図. Scavenge GCの動き
参考記事:Scavenge GCの動き
[Full GC]
Full GCは、Old領域が一杯になったときに稼動するGCであり、文字通りヒープの全領域を対象としたGCである。Scavenge GCのようにオブジェクトの移動は行わず、不要なオブジェクトを破棄してメモリの空き領域を確保していく。Scavenge GCと比較すると、対象範囲の広さと圧縮処理などを含むため低速であるという特徴がある。
GCが原因でシステムの性能が想定どおりでないという場合は、ほぼこのFull GCの頻度が高すぎることが原因であるといっていい。
図. Full GCの動き
まとめると、二つのGCには下図のような違いがある。
図. Scavenge GCとFull GCの違い
このように、Javaでは2つのGCが状況に応じて稼動することで、メモリ不足を起こさないようにシステムを管理している。
性能向上のためにはFull GCの発生を抑えることが必要
これまで見てきたようにJavaアプリの性能に大きく影響するFull GCの発生は設計で考慮することにより抑えることが可能だ。ここでは、Full GCの発生を抑えるための設計のポイントについて考えてみたい。
Full GCの発生を抑えるための3つのポイント
1.無駄なオブジェクトの生成を抑える
ある意味当たり前の話だが、生成されるオブジェクト数を減らす工夫をしよう。
また、サイズの大きなオブジェクトが多く生成されている場合、New領域で連続したメモリ領域が確保できずにFull GCが実行される場合がある。コーディングを見直し、オブジェクトのサイズそのものを抑える方法を検討してみよう。
2.オブジェクトを使い回さない
生成したオブジェクトを複数個所で使いまわすと、オブジェクトが使用中の期間が長くなり、結果として寿命が長くなる。すると、Old領域に移動する可能性が高くなりFull GCの元になる。
オブジェクトの使いまわし箇所を減らし、Scavenge GCで不要オブジェクトがきちんと破棄される状況を作ろう。
3.割り当てるメモリの大きさを変更(チューニング)する
New領域が小さい場合、オブジェクトを短い期間しか保持できない。するとMaxTenuringThreshholdの値が低下するためオブジェクトはOld領域へ移動しやすくなる。Full GCはOld領域が一杯になったときに発生するため、New領域が小さいほどにFull GCも発生しやすくなる。こうした場合、New領域を大きく設定することがFull GCの発生頻度を抑えることに繋がる場合が有る。
ただし、メモリはただ闇雲に大きくすればよいということでは無いので、チューニングは慎重に行う必要がある。なぜならば、メモリのサイズが大きくなった状態でFull GCが発生すると所要時間は当然伸びることになるため、Full GC発生の根本原因がメモリのサイズでない場合は性能を悪化させることにもつながるからだ。
まとめ
本稿では、アプリケーション性能に影響する重要なファクターであるGCの基本を紹介した。実際の現場では、細かなパラメーターを調整しながら最適な値にチューニングしていくという作業が肝要であるが、まずはGCどういった仕組みで動いていて、何故、GCの頻度が高いと性能劣化につながるのか、という全体像を理解することに本稿の内容が役立てば幸いだ。
関連記事:
コメントを残す