JVM シリーズ - HotSpot 仮想マシンのオブジェクト探求#
内容転送元: HotSpot 仮想マシンのオブジェクト探求
オブジェクトのメモリレイアウト#
HotSpot 仮想マシンでは、オブジェクトのメモリレイアウトは以下の 3 つの領域に分かれています:
- オブジェクトヘッダー(Header)
- インスタンスデータ(Instance Data)
- アライメントパディング(Padding)
オブジェクトヘッダー#
オブジェクトヘッダーには、オブジェクトが実行中に必要とするいくつかのデータが記録されています:
- ハッシュコード
- GC 世代年齢
- ロック状態フラグ
- スレッドが保持しているロック
- 偏向スレッド ID
- 偏向タイムスタンプ
オブジェクトヘッダーには型ポインタが含まれる場合があり、このポインタを通じてオブジェクトがどのクラスに属するかを特定できます。もしオブジェクトが配列であれば、オブジェクトヘッダーには配列の長さも含まれます。
インスタンスデータ#
インスタンスデータ部分はメンバー変数の値であり、親クラスのメンバー変数とこのクラスのメンバー変数が含まれます。
アライメントパディング#
オブジェクトの総長が 8 バイトの整数倍であることを保証するために使用されます。
HotSpot VM の自動メモリ管理システムは、オブジェクトのサイズが 8 バイトの整数倍でなければならないと要求します。そしてオブジェクトヘッダー部分はちょうど 8 バイトの倍数(1 倍または 2 倍)ですので、オブジェクトインスタンスデータ部分がアライメントされていない場合、アライメントパディングを通じて補完する必要があります。
アライメントパディングは必ず存在するわけではなく、特別な意味もありません。それは単にプレースホルダーの役割を果たします。
オブジェクトの作成プロセス#
クラスロードチェック#
仮想マシンが.class ファイルを解析する際に、new 命令に遭遇すると、まず定数プールにこのクラスのシンボル参照があるかどうかを確認し、そのシンボル参照が示すクラスがすでにロード、解析、初期化されているかをチェックします。もしそうでなければ、まず対応するクラスロードプロセスを実行する必要があります。
新生オブジェクトのメモリ割り当て#
オブジェクトに必要なメモリのサイズはクラスロードが完了した後に完全に確定でき、その後、ヒープから新しいオブジェクトに対応するサイズのメモリ空間を割り当てます。ヒープ内のメモリを割り当てる方法は 2 つあります:
ポインタ衝突#
Java ヒープ内のメモリが完全に整然としている場合(「コピーアルゴリズム」または「マーク整理法」を使用していることを示す)、空きメモリと使用済みメモリの間にポインタを境界点指示器として置くことで、メモリを割り当てる際にはポインタを空きメモリにオブジェクトサイズと同じ距離だけ移動させるだけで済みます。この割り当て方法は「ポインタ衝突」と呼ばれます。
空きリスト#
Java ヒープ内のメモリが整然としていない場合、使用済みメモリと空きメモリが交互に配置されている(マーク - クリア法を使用しており、フラグメントがあることを示す)ため、単純にポインタ衝突を行うことができません。この場合、VM は空きリストを維持し、どのメモリブロックが空いているかを記録する必要があります。割り当て時には空きリストから十分なサイズのメモリ空間を見つけてオブジェクトインスタンスに割り当てます。この方法は「空きリスト」と呼ばれます。
割り当て戦略#
- オブジェクトは優先的に eden 領域に割り当てられる
- 大きなオブジェクトは直接老年代に入る、大きなオブジェクトのメモリ割り当て時に割り当て保証メカニズムによるコピーで効率が低下するのを避ける(大きなオブジェクト:大量の連続メモリ空間を必要とするオブジェクト、例えば文字列、配列)
- 長期間生存するオブジェクトは老年代に入る
初期化#
メモリを割り当てた後、オブジェクト内のメンバー変数に初期値を設定し、オブジェクトヘッダー情報を設定し、オブジェクトのコンストラクタメソッドを呼び出して初期化を行います。これで、オブジェクトの作成プロセスは完了です。
オブジェクト年齢判断#
オブジェクトが Eden で生成され、最初の Minor GC 後も生存し、Survivor に収容できる場合、Survivor 空間に移動され、オブジェクトの年齢が 1 に設定されます。オブジェクトが Survivor 内で Minor GC を 1 回通過するごとに年齢が 1 歳増加し、その年齢が一定の程度(デフォルトは 15 歳)に達すると、老年代に昇格されます。オブジェクトが老年代に昇格する年齢閾値は、パラメータ -XX で設定できます。
動的オブジェクト年齢判定:
異なるプログラムのメモリ状況により適応するために、仮想マシンはオブジェクトの年齢が特定の値に達する必要がない場合があります。もし Survivor 空間内の同じ年齢のすべてのオブジェクトのサイズの合計が Survivor 空間の半分を超える場合、その年齢以上のオブジェクトは直接老年代に入ることができます、要求される年齢に達する必要はありません。
オブジェクトのアクセス方法#
すべてのオブジェクトのストレージ空間はヒープ内に割り当てられていますが、このオブジェクトの参照はスタック内に割り当てられています。つまり、オブジェクトを構築する際には両方の場所でメモリが割り当てられ、ヒープ内に割り当てられたメモリは実際にこのオブジェクトを構築し、スタック内に割り当てられたメモリはこのヒープオブジェクトへのポインタ(参照)に過ぎません。したがって、参照が格納されているアドレスのタイプに応じて、オブジェクトには異なるアクセス方法があります。
ハンドルアクセス方式#
ヒープ内には「ハンドルプール」と呼ばれるメモリ空間が必要で、ハンドルにはオブジェクトインスタンスデータと型データそれぞれの具体的なアドレス情報が含まれています。
参照型の変数はそのオブジェクトのハンドルアドレス(reference)を格納します。オブジェクトにアクセスする際には、まず参照型の変数を通じてそのオブジェクトのハンドルを見つけ、その後ハンドル内のオブジェクトのアドレスを使用してオブジェクトを見つけます。
直接ポインタアクセス方式#
参照型の変数は直接オブジェクトのアドレスを格納し、ハンドルプールを必要とせず、参照を通じてオブジェクトに直接アクセスできます。ただし、オブジェクトが存在するメモリ空間は、オブジェクトが属するクラス情報のアドレスを格納するための追加の戦略が必要です。
注意すべきは、HotSpot は第二の方法、つまり直接ポインタ方式を使用してオブジェクトにアクセスし、1 回のアドレス解決操作で済むため、性能的にはハンドルアクセス方式の 2 倍速いということです。しかし、上記のように、オブジェクトがメソッド領域内のクラス情報のアドレスを格納するための追加の戦略が必要です。