メモリー管理とは、使用していないオブジェクトをヒープから削除して、新しいオブジェクトで使用できるようにするプロセスです。この章では、基本的なメモリー管理の概念を示し、オブジェクト割当ておよびガベージ・コレクションがOracle JRockit JVMでどのように機能するかを説明します。
この章の内容は次のとおりです。
注意: この章では主にメモリー管理の概念を説明します。メモリー管理システムのチューニングの詳細は、『Oracle JRockitパフォーマンス・チューニング・ガイド』のメモリー管理システムのチューニングに関する項を参照してください。 |
Javaオブジェクトは、JVMの起動時に作成されるメモリー・スタックのセクションである、ヒープと呼ばれる領域に存在します。ヒープは、アプリケーションの実行中に拡大または縮小することがあります。
ヒープが一杯になると、ガベージが収集されます。JRockit JVMにより、使用中のオブジェクト(ライブ・オブジェクト)を含むメモリー領域が特定されます。次に、ライブ・オブジェクトを含まないメモリー領域が解放されて、新しいオブジェクトを割り当てることができるようになります。
注意: JRockit JVMはヒープ以外にもメモリーを使用します。Javaメソッド、スレッド・スタック、ネイティブ・ハンドルおよびJVMの内部データ構造は、ヒープ以外のメモリー領域に割り当てられます。 |
ヒープは、ナーサリ(若い領域)と古い領域という、2つの世代に分割されることがあります。ナーサリは、新しいオブジェクトを割り当てるために予約されているヒープの一部です。ナーサリが一杯になると、特別な若いコレクションを実行することによりガベージが収集され、ナーサリに長く存在しているオブジェクトは古い領域に昇格(移動)されます。これにより、オブジェクトの割当てに使用できるようにナーサリが解放されます。古い領域が一杯になると、そこでガベージが収集されます。このプロセスは古いコレクションと呼ばれます。
注意: 若いコレクションの最中に、ナーサリからオブジェクトを昇格するための古い領域に残されたメモリーが少なくなる場合があります。古い領域でメモリーが使用できるようになるまで、古い領域への昇格用として識別されたオブジェクトは、代替的にナーサリ内で昇格されます。これによってナーサリの断片化が生じ、この現象は昇格エラーとして知られています。R28.1で、JRockit JVMは、予定されている若いコレクションで昇格の障害が発生する可能性を査定し、古いコレクションをトリガーすることでその障害の発生を防ぎます。 |
マルチ世代のヒープは全ガベージ・コレクションの時間の削減に利用できます。ヒープに割り当てられるオブジェクトのほとんどは短い間しか生存しません。若いコレクションは、新しく割り当てられたオブジェクトでまだ使用中のものをナーサリですばやく検出し、ナーサリの外へ移動させます。通常、若いコレクションは、古いコレクションや一世代のヒープ(ナーサリのないヒープ)のガベージ・コレクションよりも格段に速く所定のメモリー領域を解放します。
ナーサリの一部は、若いコレクションの開始直前に割り当てられていたオブジェクトを保管するための保持領域として確保されます。これらのオブジェクトについて、次の若いコレクションまではガベージ・コレクションは行われません。
オブジェクトの割当て時に、JRockit JVMは 小規模オブジェクトと 大規模オブジェクトを区別します。オブジェクトが大規模であると見なされるかどうかは、ヒープ・サイズ、ガベージ・コレクション方式、使用しているプラットフォームなどによって決まりますが、通常の基準は2KBから128KB程度です。詳細は、『Oracle JRockitコマンドライン・リファレンス』の-XXtlaSize
コマンドライン・オプションに関するドキュメントを参照してください。
小規模なオブジェクトはスレッド・ローカル領域(TLA)内に割り当てられます。これはヒープから予約されている空きのメモリー・チャンクであり、Javaスレッドに独占的に与えられます。このスレッドは、別のスレッドと同期しなくても、自分専用のTLAにオブジェクトを割り当てることができます。TLAが一杯になると、スレッドは新しいTLAを要求します。ナーサリが存在していれば、TLAはナーサリで確保されます。ナーサリがない場合、TLAはヒープのどこかで確保されます。
TLAよりも大きいサイズのオブジェクトは、直接ヒープに割り当てられます。大規模なオブジェクトの割当てでは、Javaスレッド間の同期をより頻繁に行う必要があります。このため、JRockit JVMでは、同期の必要性を軽減して割当て速度を上げるために、様々なサイズの空きチャンクによるキャッシュ・システムを使用しています。
ガベージ・コレクションは、新しいオブジェクトに割り当てるためにヒープの領域を解放します。
次の項ではJRockit JVMのガベージ・コレクションについて説明します。
JRockit JVMは、ヒープ全体のガベージ・コレクションを行う際に マーク・アンド・スイープのガベージ・コレクション・モデルを使用します。
マーク・フェーズでは、Javaスレッド、ネイティブ・ハンドルおよびその他のルート・ソースから直接到達可能なすべてのオブジェクトや、最初のオブジェクトのセットから到達可能なオブジェクトなどは、使用中(ライブ・オブジェクト)とマークされます。残りのオブジェクトはガベージと見なされます。
スイープ・フェーズでは、ライブ・オブジェクト間のギャップを特定するためにヒープがトラバースされます。ギャップは、フリー・リストに記録され、新しいオブジェクトの割当てに利用できるようになります。
JRockit JVMは、マーク・アンド・スイープ・モデルの改良された2つの方式、モーストリ・コンカレントとパラレルを提供します。この2つの方式を組み合せて、たとえば、モーストリ・コンカレント・マークをパラレル・スイープとともに実行することもできます。
モーストリ・コンカレント・マーク・アンド・スイープ方式
モーストリ・コンカレント・マーク・アンド・スイープ方式(通常はコンカレント・ガベージ・コレクションと呼ばれます)では、ガベージ・コレクション・プロセスの大部分でJavaスレッドを連続して実行できます。ただし、スレッドは、同期のために数回停止する必要があります。
モーストリ・コンカレント・マーク・フェーズは、次の4段階に分かれます。
初期マーキング
ライブ・オブジェクトのルート・セットが識別されます。これは、Javaスレッドの休止中に行われます。
コンカレント・マーキング
ルート・セットからの参照に従って、ヒープ内の残りのライブ・オブジェクトが検索されてマークされます。これは、Javaスレッドの実行中に行われます。
プレクリーニング
コンカレント・マーク・フェーズにおけるヒープの変更が識別され、その他のライブ・オブジェクトが検索されてマークされます。これは、Javaスレッドの実行中に行われます。
最終マーキング
プレクリーニング・フェーズにおける変更が識別され、その他のライブ・オブジェクトが検索されてマークされます。これは、Javaスレッドの休止中に行われます。
モーストリ・コンカレント・スイープ・フェーズは、次の4段階で構成されます。
ヒープの半分のスイープ
これは、Javaスレッドの実行中に行われ、現在スイープされていないヒープの部分でオブジェクト割当てを続行できます。
残りの半分に切り替えるための短期間の休止
ヒープの残りの半分のスイープ
これは、Javaスレッドの実行中に行われ、最初にスイープされたヒープの部分でオブジェクト割当てを続行できます。
同期、および統計の記録のための短期間の休止
パラレル・マーク・アンド・スイープ方式
パラレル・マーク・アンド・スイープ方式(「パラレル・ガベージ・コレクタ」とも呼ばれる)は、システム内の利用可能なCPUすべてを使用して、可能な限り最速でガベージ・コレクションを行います。パラレル・ガベージ・コレクションの間、すべてのJavaパレットは休止します。
(4.1項「ヒープとナーサリ」で前述したように)ナーサリが存在する場合、ナーサリで、若いコレクションという特別なガベージ・コレクションが行われます。ナーサリを使用するガベージ・コレクション方式は、 世代別ガベージ・コレクションと呼ばれます。
JRockit JVMで使用される若いコレクタは、ナーサリ内の保持領域外にあるすべてのライブ・オブジェクトを識別し、古い領域へ昇格します。この作業は、すべての利用可能なCPUを使用して並行して行われます。若いコレクションの間、Javaスレッドは休止します。
JRockit JVMでは、アプリケーションのスループットを最適化するガベージ・コレクション方式が自動的に選択されます。
次のガベージ・コレクション・モードを使用できます。
throughput(デフォルト・モード): アプリケーションのスループットを最大化するためにガベージ・コレクタを最適化します。
pausetime: 休止時間を短く、均等にするためにガベージ・コレクタを最適化します。
deterministic(Oracle JRockit Real Timeの一部としてのみ使用可能): 休止時間を非常に短く、確定的にするためにガベージ・コレクタを最適化します。
詳細は、『Oracle JRockitパフォーマンス・チューニング・ガイド』のガベージ・コレクタの選択およびチューニングに関する項を参照してください。
ガベージ・コレクションの後に、ヒープが 断片化する場合があります。無数の空き領域が存在しますが個々の空き領域が小さいため、大規模オブジェクトの割当てができなくなるおそれがあります。最少TLAのサイズよりも小さな空き領域は使用不可能であり、将来のガベージ・コレクションによって、TLAに必要な大きさの領域を作成できるだけの隣接した領域が解放されるまで、ガベージ・コレクタはこれらの領域を ダーク・マターとして廃棄し続けます。
断片化を緩和するために、JRockit JVMでは、ガベージ・コレクション(古いコレクション)を行うたびにヒープの部分的な圧縮を行います。圧縮では、ヒープ内のオブジェクトを後方に寄せ集めることにより、ヒープの先頭付近に大きな空き領域を作成することができます。使用されるガベージ・コレクションのモードに応じて、圧縮領域のサイズ(および位置)と圧縮方式が、高度なヒューリスティックによって選択されます。
圧縮は、スイープ・フェーズの最初に行われますが、その間Javaスレッドはすべて休止します。
圧縮のチューニング方法の詳細は、『Oracle JRockitパフォーマンス・チューニング・ガイド』のメモリーの圧縮のチューニングに関する項を参照してください。
外部および内部の圧縮
JRockit JVMは、外部圧縮と内部圧縮という2つの圧縮方式を使用します。
外部圧縮では、圧縮領域内のオブジェクトを、圧縮領域の外部、ヒープ内のできるだけ後方の任意の位置に移動(退避)させます。内部圧縮では、圧縮領域内のオブジェクトを、圧縮領域内のできるだけ後方に移動させることで近くに寄せ集めます。
JVMは、現在のガベージ・コレクション・モードと圧縮領域の位置に基づいて圧縮方式を選択します。外部圧縮は通常、ヒープの先頭付近で使用され、内部圧縮はオブジェクトの密度がより高いヒープの最後尾付近で使用されます。
圧縮領域のサイズと位置
圧縮領域のサイズと位置は、ガベージ・コレクション・モードと、Javaヒープの現在の状態によって決まります。
領域内のオブジェクトへの参照数(オブジェクトの密度)が大きい場合、圧縮領域のサイズは小さくなります。通常は、ヒープの最後尾付近の方が、先頭付近よりもオブジェクトの密度が高くなります。ただし、ヒープの最先端には最近割り当てられたオブジェクトが存在するので、密度は高くなります。このように、圧縮領域は通常、ヒープの最後尾付近のほうが先頭部分よりも小さくなります。