プライマリ・コンテンツに移動
Java Platform, Standard Edition HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド
リリース10
E94983-01
目次へ移動
目次

前
前へ
次

3 ガベージ・コレクタの実装

Java SEプラットフォームの強みの1つは、メモリー割当てとガベージ・コレクションの複雑さが開発者から隠蔽されることです。

ただし、ガベージ・コレクションが重要なボトルネックになった場合、実装面を理解しておくと役に立ちます。ガベージ・コレクタでは、アプリケーションがどのようにオブジェクトを使用するかについて前提を立て、それを調整可能なパラメータに反映して、これらのパラメータを調整することで、抽象化の威力を犠牲にすることなくパフォーマンスを改善できます。

世代別ガベージ・コレクション

実行中のプログラムで他のライブ・オブジェクトから参照されなくなると、オブジェクトはガベージ(廃棄物)とみなされ、そのメモリーはVMで再使用できるようになります。

理論上、最も簡単なガベージ・コレクション・アルゴリズムは、到達可能なすべてのオブジェクトを1つずつ実行するたびに調べます。残りのオブジェクトはガベージとみなされます。この方法ではライブ・オブジェクトの数に比例した時間がかかるので、大量のライブ・データが保持される大規模なアプリケーションでは使用できません。

Java HotSpot VMには様々な種類のガベージ・コレクション・アルゴリズムが組み込まれており、それらはすべて世代別コレクションと呼ばれる技術を使用しています。ネイティブ・ガベージ・コレクションでは常にヒープ内のすべてのライブ・オブジェクトを調べますが、世代別コレクションでは、ほとんどのアプリケーションで経験的に得られたいくつかの特徴を利用して、未使用(ガベージ)オブジェクトの回収に必要な作業を最小限に抑えます。このように得られた特徴の中で最も重要なのは弱い世代別仮説であり、この説では、ほとんどのオブジェクトは短命であることが示されています。

図3-1に青で示した領域が、オブジェクトの典型的な寿命分布です。X軸は、オブジェクトの寿命を、割り当てられたバイト数で示しています。Y軸のバイト数は、その寿命を持つオブジェクト群の総バイト数です。左端の鋭いピークは、割当て後にすぐに回収できる(「寿命を終えた」)オブジェクトを表しています。たとえば、イテレータ・オブジェクトは、1つのループ期間だけ生存するのが一般的です。

図3-1 オブジェクトの典型的な寿命分布

図3-1の説明が続きます
「図3-1 オブジェクトの典型的な寿命分布」の説明

より寿命が長いオブジェクトもあるので、分布は右側に伸びています。たとえば、初期化時に割り当てられ、VMが終了するまで生き続けるオブジェクトはよくあります。この2種類の両極端なオブジェクトの中間に、なんらかの中間演算の間だけ生存するオブジェクトがあり、ここでは初期ピークの右側のこぶとして現れています。これとはまったく違う分布になるアプリケーションもありますが、驚くほど多くのアプリケーションで、この一般的な形状が当てはまります。オブジェクトの大半が「短命」であるという事実に着目すると、コレクションの効率化が可能になります。

世代

このシナリオを最大限に活用するのが、メモリーの世代(年齢ごとにオブジェクトを保持するメモリー・プール)別管理です。ある世代がいっぱいになったときに、その世代でガベージ・コレクションを実行します。

大半のオブジェクトは若いオブジェクト用のプール(若い世代)に割り当てられ、ほとんどのオブジェクトはそこで寿命を終えます。若い世代がいっぱいになると、マイナー・コレクションが発生して、若い世代のみが収集されます。他の世代のガベージは回収されません。このようなコレクションのコストは、一次的には、収集されるライブ・オブジェクトの数に比例します。寿命を終えたオブジェクトでいっぱいの若い世は非常にすばやく収集されます。一般に、各マイナー・コレクションで生き残ったオブジェクトの一部は若い世代から古い世代に移されます。最終的に古い世代がいっぱいになって収集が必要になると、メジャー・コレクションが発生し、ヒープ全体が収集されます。メジャー・コレクションは、非常に多くのオブジェクトを対象とするため、通常はマイナー・コレクションよりはるかに長い時間がかかります。図3-2に、シリアル・ガベージ・コレクタにおける世代のデフォルトの配置を示します。

図3-2 シリアル・コレクタにおける世代のデフォルトの配置

図3-2の説明が続きます
「図3-2 シリアル・コレクタにおける世代のデフォルトの配置」の説明

起動時に、Java HotSpot VMはアドレス空間にJavaヒープ全体を確保しますが、必要にならないかぎり、物理メモリーをこれに割り当てません。Javaヒープを対象とするアドレス空間全体は、論理的に若い世代と古い世代に分けられます。オブジェクト・メモリー用に確保されるアドレス空間全体は、若い世代と古い世代に分けることができます。

若い世代はEden領域と2つのSurvivor領域で構成されています。ほとんどのオブジェクトは最初にEdenに割り当てられます。Survivor領域の一方は常に空で、Eden内およびガベージ・コレクション時のもう一方のSurvivor領域のライブ・オブジェクトのコピー先として使用されます。ガベージ・コレクション後、Edenおよびコピー元のSurvivor領域は空です。次のガベージ・コレクションでは、2つのSurvivor領域の目的が交換されます。直近でいっぱいになった領域がライブ・オブジェクトのコピー元であり、もう一方のSurvivor領域にコピーされます。このようにして、そのオブジェクトが何回かコピーされるか、十分な領域がなくなるまで、Survivor領域間でオブジェクトがコピーされます。これらのオブジェクトは、古いリージョンにコピーされます。このプロセスはエージングとも呼ばれます。

パフォーマンスに関する考慮事項

ガベージ・コレクションで測定するのは、主にスループットとレイテンシです。

  • スループットは、長期間で見たときの、ガベージ・コレクション以外の処理に使用される時間の割合です。スループットには割当てにかかる時間も含まれます(ただし、通常は割当ての速度を調整する必要はありません)。

  • レイテンシはアプリケーションの応答性です。ガベージ・コレクションの一時停止は、アプリケーションの応答性に影響を及ぼします。

ガベージ・コレクションの要件は、ユーザーによって様々です。たとえば、ガベージ・コレクション中の一時停止は許容できるか、ネットワーク遅延の前では目立たないため、Webサーバーの適切なメトリックはスループットだと考えるユーザーもいます。これに対して、対話型のグラフィック・プログラムでは、ほんの短い一時停止でも、ユーザーの操作性を損う可能性があります。

これ以外のことを重視するユーザーもいます。フットプリントは、プロセスの作業セットをページ数やキャッシュ・ライン数で測定したものです。物理メモリーが制限されていたり、プロセス数が多いシステムでは、フットプリントがスケーラビリティを左右する可能性があります。機敏性は、オブジェクトが寿命を終えてから、そのメモリーが使用可能になるまでの時間で、Remote Method Invocation (RMI)などの分散システムでは、重要な考慮事項になります。

一般に、特定の世代のサイズを選択することは、これらの考慮事項とのトレードオフです。たとえば、若い世代を非常に大きくすると、スループットが最大になりますが、フットプリント、機敏性および一時停止時間が犠牲になります。若い世代を小さくすると、若い世代の一時停止を最小にできますが、スループットが犠牲になります。1つの世代のサイズを変更しても、別の世代のコレクションの頻度や一時停止時間には影響しません。

世代のサイズを選択する方法は1つではありません。アプリケーションによるメモリーの使用方法とユーザー要件によって、最適な選択が決まります。そのため、仮想マシンによるガベージ・コレクタの選択が必ずしも最適とはかぎらないので、コマンド行オプションを使用してオーバーライドできます(「ガベージ・コレクションのパフォーマンスに影響する要因」を参照)。

スループットおよびフットプリントの測定

スループットとフットプリントの最適な測定方法は、アプリケーション専用のメトリックスを使用するやり方です。

たとえば、Webサーバーのスループットはクライアント負荷生成プログラムを使用してテストでき、そのサーバーのフットプリントは、pmapコマンド(Solarisオペレーティング・システムの場合)を使用して測定できます。これに対して、ガベージ・コレクションによる一時停止は、仮想マシン自体の診断出力をチェックすることで簡単に推測できます。

コマンド行オプション-verbose:gcを使用すると、コレクションごとにヒープとガベージ・コレクションに関する情報が出力されます。次はその例です。

[15,651s][info ][gc] GC(36) Pause Young (G1 Evacuation Pause) 239M->57M(307M) (15,646s, 15,651s) 5,048ms
[16,162s][info ][gc] GC(37) Pause Young (G1 Evacuation Pause) 238M->57M(307M) (16,146s, 16,162s) 16,565ms
[16,367s][info ][gc] GC(38) Pause Full (System.gc()) 69M->31M(104M) (16,202s, 16,367s) 164,581ms

この出力では、2つの若いコレクションの後に、System.gc()への呼出しでフル・コレクションがアプリケーションによって開始されます。行の先頭は、アプリケーションが起動された時間を示すタイム・スタンプです。その次は、この行のログ・レベル(info)とタグ(gc)に関する情報です。この後に、GC識別番号が続きます。この場合、番号が36、37および38の3つのGCがあります。その後、GCのタイプとGCが起動された原因が記録されます。この後、メモリー消費に関する情報の一部が記録されます。このログで使用する形式は、"GC前の使用量" -> "GC後の使用量" ("ヒープ・サイズ")です。

この例の最初の行は、239M->57M(307M)となっています。これは、GCの前は239MBが使用されていて、GCでメモリーの大部分がクリーン・アップされ、57MBが残ったことを示します。ヒープ・サイズは307MBです。この例では、フルGCでヒープが307MBから104MBに縮小されていることに注意してください。メモリー使用量情報の後、GCの開始時間と終了時間が期間(終了 - 開始)とともに記録されます。

-verbose:gcコマンドは-Xlog:gcの別名です。-Xlogは、HotSpot JVMでのロギング用の一般的なロギング構成オプションです。これはタグベースのシステムであり、gcはタグの1つです。GCの実行内容に関する詳細情報を取得するには、gcタグと他のタグを含むメッセージを出力するようにロギングを構成します。これを実行するコマンド行オプションは、-Xlog:gc*です。

次に、-Xlog:gc*で記録されたG1の若いコレクションの一例を示します。

[10.178s][info][gc,start ] GC(36) Pause Young (G1 Evacuation Pause) 
[10.178s][info][gc,task ] GC(36) Using 28 workers of 28 for evacuation 
[10.191s][info][gc,phases ] GC(36) Pre Evacuate Collection Set: 0.0ms
[10.191s][info][gc,phases ] GC(36) Evacuate Collection Set: 6.9ms 
[10.191s][info][gc,phases ] GC(36) Post Evacuate Collection Set: 5.9ms 
[10.191s][info][gc,phases ] GC(36) Other: 0.2ms 
[10.191s][info][gc,heap ] GC(36) Eden regions: 286->0(276) 
[10.191s][info][gc,heap ] GC(36) Survivor regions: 15->26(38)
[10.191s][info][gc,heap ] GC(36) Old regions: 88->88 
[10.191s][info][gc,heap ] GC(36) Humongous regions: 3->1 
[10.191s][info][gc,metaspace ] GC(36) Metaspace: 8152K->8152K(1056768K)
[10.191s][info][gc ] GC(36) Pause Young (G1 Evacuation Pause) 391M->114M(508M) 13.075ms 
[10.191s][info][gc,cpu ] GC(36) User=0.20s Sys=0.00s Real=0.01s

注意:

-Xlog:gc*によって生成される出力形式は、今後のリリースで変更されることがあります。