2.2.1 SerialGCの仕組み
SerialGCの仕組みについて説明します。
- 〈この項の構成〉
(1) SerialGCの概要
GCは,プログラムが使用し終わったメモリ領域を自動的に回収して,ほかのプログラムが利用できるようにするための技術です。
GCの実行中は,プログラムの処理が停止します。このため,GCを適切に実行できるかどうかが,システムの処理性能に大きく影響します。
プログラムの中でnewによって作成されたJavaオブジェクトは,JavaVMが管理するメモリ領域に格納されます。Javaオブジェクトが作成されてから不要になるまでの期間を,Javaオブジェクトの寿命といいます。
Javaオブジェクトには,寿命の短いオブジェクトと寿命の長いオブジェクトがあります。サーバサイドで動作するJavaアプリケーションの場合,リクエストやレスポンス,トランザクション管理などで,多くのJavaオブジェクトが作成されます。これらのJavaオブジェクトは,その処理が終わると不要になる,寿命が短いオブジェクトです。一方,アプリケーションの動作中使用され続けるJavaオブジェクトは,寿命が長いオブジェクトです。
効果的なGCを実行するためには,寿命の短いオブジェクトに対してGCを実行して,効率良くメモリ領域を回収することが必要です。また,繰り返し使用される寿命の長いオブジェクトに対する不要なGCを抑止することが,システムの処理性能の低下防止につながります。これを実現するのが,世代別GCです。
世代別GCでは,Javaオブジェクトを,寿命が短いオブジェクトが格納されるNew領域と,寿命が長いオブジェクトが格納されるTenured領域に分けて管理します。New領域はさらに,newによって作成されたばかりのオブジェクトが格納されるEden領域と,1回以上のGCの対象になり,回収されなかったオブジェクトが格納されるSurvivor領域に分けられます。New領域内で一定回数以上のGCの対象になったJavaオブジェクトは,長期間必要なJavaオブジェクトと判断され,Tenured領域に移動します。
世代別GCで管理するメモリ空間とJavaオブジェクトの概要を次の図に示します。
SerialGCの世代別GCで実行されるGCには,次の2種類があります。
-
Eden領域とSurvivor領域を対象にしたGCです。Javaオブジェクトの作成によって,Eden領域を使い切ると発生します。
-
Tenured領域も含む,JavaVM固有領域全体を対象にしたGCです。Tenured領域を一定サイズまで使うと発生します。
一般的に,CopyGCの方が,FullGCよりも短い時間で処理できます。
次に,GCの処理について,あるJavaオブジェクト(オブジェクトA)を例にして説明します。
- Eden領域で実行される処理
-
オブジェクトAの作成後,1回目のCopyGCが実行された時点で使用されていない場合,オブジェクトAは破棄されます。
1回目のCopyGCが実行された時点で使用されていた場合,オブジェクトAはEden領域からSurvivor領域に移動します。
- Survivor領域で実行される処理
-
Survivor領域に移動したオブジェクトAは,そのあと何回かCopyGCが実行されると,Survivor領域からTenured領域に移動します。移動する回数のしきい値は,JavaVMオプションやJavaヒープの利用状況によって異なります。「図2-1」では,しきい値をn回としています。
Survivor領域への移動後,n回目未満のCopyGCが実行された時点でオブジェクトAが使用されていなかった場合,オブジェクトAはそのCopyGCで破棄されます。
- Tenured領域で実行される処理
-
オブジェクトAがTenured領域に移動した場合,そのあとのCopyGCでオブジェクトAが破棄されることはありません。CopyGCは,Eden領域とSurvivor領域だけを対象としているためです。
このようにしてオブジェクトが移動することで,Tenured領域の使用サイズは増加します。Tenured領域を一定サイズまで使うと,FullGCが発生します。
JavaVMのチューニングでは,JavaVMオプションでそれぞれのメモリ空間のサイズや割合を適切に設定することで,不要なオブジェクトがTenured領域に移動することを抑止します。これによって,FullGCが頻発することを防ぎます。
(2) オブジェクトの寿命と年齢の関係
オブジェクトがCopyGCの対象になった回数をオブジェクトの年齢といいます。
オブジェクトの寿命と年齢の関係を次の図に示します。
アプリケーションが開始して初期化処理が完了したあとで,何度かのCopyGCが実行されると,長期間必要になる寿命の長いオブジェクトはTenured領域に移動します。このため,アプリケーションの開始後しばらくすると,Javaヒープの状態は安定し,作成されるJavaオブジェクトとしては,寿命が短いオブジェクトが多くなります。特に,New領域のチューニングが適切にできている場合,Javaヒープが安定したあとの大半のオブジェクトは,1回目のCopyGCで回収される,寿命が短いオブジェクトになります。
(3) CopyGCの仕組み
JavaVMでは,CopyGCの対象になるNew領域のメモリ空間を,Eden領域,Survivor領域に分けて管理します。さらに,Survivor領域は,From空間とTo空間に分けられます。From空間とTo空間は,同じメモリサイズです。
New領域の構成を次の図に示します。
Eden領域は,newによって作成されたオブジェクトが最初に格納される領域です。プログラムでnewが実行されると,Eden領域のメモリが確保されます。
Eden領域がいっぱいになると,CopyGCが実行されます。CopyGCでは,次の処理が実行されます。
-
Eden領域,およびSurvivor領域のFrom空間にあるJavaオブジェクトのうち,使用中のJavaオブジェクトが,Survivor領域のTo空間にコピーされます。使用されていないJavaオブジェクトは破棄されます。
-
Survivor領域のTo空間とFrom空間が入れ替わります。
この結果,Eden領域とTo空間は空になり,使用中のオブジェクトはFrom空間に存在することになります。
CopyGC実行時に発生するオブジェクトの移動を次の図に示します。
このようにして,使用中のオブジェクトは,CopyGCが発生するたびに,From空間とTo空間を行ったり来たりします。ただし,寿命の長いオブジェクトを行き来させ続けると,コピー処理の負荷などが問題になります。これを防ぐために,From空間とTo空間でJavaオブジェクトを入れ替える回数にしきい値を設定して,年齢がしきい値に達したJavaオブジェクトはTenured領域に移動させるようにします。
(4) オブジェクトの退避
年齢がしきい値に達していないJavaオブジェクトがTenured領域に移動することを,退避といいます。退避は,CopyGC実行時にEden領域およびFrom空間で使用中のオブジェクトが多くなり,移動先であるTo空間のメモリサイズが不足する場合に発生します。この場合,To空間に移動できなかったオブジェクトが,Tenured領域に移動します。
オブジェクトの退避を次の図に示します。
オブジェクトの退避が発生した場合,Tenured領域に本来格納されないはずの寿命の短いオブジェクトが格納されます。これが繰り返されると,CopyGCで回収されるはずのオブジェクトがメモリ空間に残っていくため,Javaヒープのメモリ使用量が増加していき,最終的にはFullGCが発生します。
オブジェクトの退避が発生した場合のメモリ使用量の変化について,次の図に示します。
FullGCでは,システムが数秒から数十秒停止することがあります。
したがって,メモリ空間の構成とメモリサイズを検討するときには,オブジェクトの退避が発生しないように,Eden領域とSurvivor領域のバランスを検討する必要があります。
(5) SerialGC使用時のJavaVMで使用するメモリ空間の構成とJavaVMオプション
ここでは,SerialGC使用時のJavaVMで使用するメモリ空間の構成と,JavaVMオプションについて説明します。
JavaVMでは,JavaVM固有領域とOS固有領域という,2種類のメモリ空間を使用します。
JavaVMで使用するメモリ空間の構成を次の図に示します。なお,図中の番号は,「表2-3 JavaVMメモリ空間のサイズや割合などを指定するJavaVMオプション」の項番と対応しています。
それぞれの領域について説明します。なお,Eden領域,Survivor領域,およびTenured領域を合わせた領域を,Javaヒープといいます。
- Eden領域
-
newによって作成されたJavaオブジェクトが最初に格納される領域です。
- Survivor領域
-
New領域に格納されていたJavaオブジェクトのうち,CopyGC実行時に破棄されなかったJavaオブジェクトが格納される領域です。Survivor領域は,From空間とTo空間で構成されます。From空間とTo空間のサイズは同じです。
- Tenured領域
-
長期間必要であると判断されたJavaオブジェクトが格納される領域です。Survivor領域で指定回数を超えてCopyGCの実行対象となり,破棄されなかったJavaオブジェクトが,この領域に移動されます。
- Metaspace領域
-
ロードされたclassなどの情報が格納される領域です。
ただし,圧縮オブジェクトポインタ機能が有効な場合,Metaspace領域内にCompressed Class Spaceという領域が作成されます。この領域に,Javaヒープ内のオブジェクトから参照されるクラス情報が配置されます。そして,それ以外のメソッド情報などがCompressed Class Space以外のMetaspace領域に配置されます。圧縮オブジェクトポインタ機能については,「4.15 圧縮オブジェクトポインタ機能」を参照してください。
- Cヒープ領域
-
JavaVM自身が使用する領域です。JNIで呼び出されたネイティブライブラリでも使用されます。
例えば,次のような領域があります。
- コードキャッシュ領域
-
JITコンパイルによって生成されたJITコンパイルコードが格納される領域です。
JavaVMは,呼び出し回数やループ回数が多いJavaメソッドをJITコンパイルして実行することで,処理を高速化します。
- メモ
-
コードキャッシュ領域の最大サイズについて
コードキャッシュ領域の最大サイズは,ReservedCodeCacheSizeオプションに指定します。
ReservedCodeCacheSizeオプションには,デフォルト値以上の値を指定してください。Java HotSpot VMのオプションのデフォルト値については,マニュアル「uCosminexus Application Runtime ユーザーズガイド」を参照してください。
また,コードキャッシュ領域が枯渇していた場合,または枯渇するおそれがある場合は,コードキャッシュ領域の拡張を検討してください。
コードキャッシュ領域を拡張する場合は,次の点に注意してください。
-
JITコンパイルコードのサイズは計算で見積もることができません。そのため,Javaアプリケーション実行環境でコードキャッシュ領域の使用量を実測し,システムが使用するコードキャッシュ領域(64ビット版で最大2メガバイト)の使用量を考慮した上で,コードキャッシュ領域の最大サイズを見積もってください。
-
仮想メモリの使用量の見積もりについては,「2.1.2 プロセスごとに使用するメモリの見積もり」を参照してください。
-
- スタック領域
-
Javaスレッドのスタック領域です。
それぞれの領域のサイズや割合などを指定するJavaVMオプションを次の表に示します。なお,表の項番は,「図2-7」と対応しています。
項番 |
オプション名 |
オプションの意味 |
---|---|---|
1 |
-Xmx<size> |
Javaヒープの最大サイズを設定します。 |
2 |
-Xms<size> |
Javaヒープの初期サイズを設定します。 |
3 |
-XX:MaxMetaspaceSize=<size> |
Metaspace領域の最大サイズを設定します。 |
4 |
-XX:MetaspaceSize=<size> |
Metaspace領域の初期サイズを設定します。 |
5 |
-Xmn<size> |
New領域の初期値および最大値を設定します。 |
6 |
-Xss<size> |
1スレッド当たりのスタック領域の最大サイズを設定します。 |
7 |
-XX:ReservedCodeCacheSize=<size> |
JavaVMが使用する領域のうち,コードキャッシュ領域の最大サイズを設定します。 |
8 |
-XX:NewRatio=<value> |
New領域に対するTenured領域の割合を設定します。 <value>が2の場合は,New領域とTenured領域の割合が,1:2になります。 |
9 |
-XX:SurvivorRatio=<value> |
Survivor領域のFrom空間とTo空間に対するEden領域の割合を設定します。 <value>に8を設定した場合は,Eden領域,From空間,To空間の割合が,8:1:1になります。 |
10 |
-XX:TargetSurvivorRatio=<value> |
CopyGC実行後のSurvivor領域内でJavaオブジェクトが占める割合の目標値を設定します。 |
11 |
-XX:MaxTenuringThreshold=<value> |
CopyGC実行時に,From空間とTo空間でJavaオブジェクトを入れ替える回数の最大値を設定します。 設定した回数を超えて入れ替え対象になったJavaオブジェクトは,Tenured領域に移動されます。 |
- 重要
-
Javaヒープ領域,Metaspace領域,Compressed Class Space,Cヒープ領域のどれかが不足するとOutOfMemoryが発生し,メモリ不足が解消されないかぎり正常に稼働できない状態が長く続くことになります。
これに対して,OutOfMemory発生時のシステムへの影響を小さくするために,次のオプションを使用できます。
- -XX:+HitachiOutOfMemoryAbort(OutOfMemory発生時強制終了機能)
-
OutOfMemory発生時強制終了機能は,Javaヒープ不足やMetaspace領域不足,Compressed Class Space不足などが原因でOutOfMemoryが発生した場合に,アプリケーションを強制終了するための機能です。Javaヒープ領域,Metaspace領域,Compressed Class Space,Cヒープ領域のメモリ不足によってOutOfMemoryが発生したときに,アプリケーションを強制終了します。
アプリケーションサーバの自動再起動や待機系システムへの自動切り替えなどの仕組みを前提として,アプリケーションサーバプロセスの生存を監視している場合には,このオプションを有効にすることで,正常な状態への回復を促す効果があります。
オプションの詳細については,「-XX:[+|-]HitachiOutOfMemoryAbort(強制終了オプション)」を参照してください。
(6) GCの発生とメモリ空間の関係
GCは,メモリ空間の使用状況に応じて発生します。ここでは,GCが発生するタイミングについて説明します。
- 重要
-
RMIを使ってリモートオブジェクトの登録や参照をすると,定期的にGCが発生することがあります。GCが発生するタイミングは,次のどちらかのプロパティにミリ秒単位で指定できます。デフォルトは3600000ミリ秒(1時間)です。
-
sun.rmi.dgc.client.gcIntervalプロパティ
-
sun.rmi.dgc.server.gcIntervalプロパティ
GCが発生するタイミングを変更する場合は,どちらかのプロパティにミリ秒単位で任意の値を指定してください。なお,指定できる値の範囲は,1〜Long.MAX_VALUE-1です。プロパティの詳細は,Oracle社のWebページを参照してください。
-
(a) CopyGCが発生するタイミング
CopyGCは,次のタイミングで発生します。
-
Eden領域へのアロケーションで空き領域が不足した場合
-
jheapprofコマンドに-copygcオプションを指定して実行した場合
(b) FullGCが発生するタイミング
FullGCは,次のタイミングで発生します。
-
New領域(Eden領域とSurvivor領域の合計)で使用しているメモリサイズがTenured領域の最大値に対する未使用メモリサイズを上回っている状態の時に,Eden領域へのアロケーションで空き領域が不足した場合
- 重要
-
上記のタイミングで必ずFullGCが発生するわけではありません。この状況になった場合,FullGCを発生させる必要があるかどうかについては,過去のCopyGCでのTenured領域への移動量の実績値に基づいて,JavaVMが決定します。
-
CopyGCの実施の結果,New領域(Eden領域とSurvivor領域の合計)からTenured領域へのオブジェクトの移動に失敗した場合
-
New領域とTenured領域のそれぞれの未使用メモリサイズを上回るメモリサイズ(Javaオブジェクトのサイズ)のアロケーション要求があった場合
-
CopyGCの実施の結果,次のどちらかの状態になった場合
-
確保済みTenured領域の未使用メモリサイズが10,000バイトを下回った場合
-
CopyGC実施時のTenured領域へのオブジェクトの移動によって,確保済みTenured領域の拡張が発生した場合
-
-
java.lang.System.gc()メソッドが実行された場合
-
Metaspace領域にアロケーションしたいメモリサイズが確保済みMetaspace領域の未使用メモリサイズを上回る場合
-
javagcコマンドを実行した場合
-
jheapprofコマンドを実行した場合
JavaVMのチューニングでは,主に1.と3.の発生を抑えることを検討します。
- メモ
-
FullGCが発生した場合の要因は,拡張verbosegc情報を使用して確認できます。FullGC発生時に要因を確認する方法については,「2.2.8 拡張verbosegc情報を使用したFullGCの要因の分析方法」を参照してください。