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 倍速いことです。しかし、上記のように、オブジェクトがメソッド領域内のクラス情報のアドレスを格納するための追加の戦略が必要です。