JVMでのオブジェクト作成/レイアウト/アクセス

1. HotSpot 仮想マシン内のオブジェクト

Java 仮想マシンのランタイム データ領域を理解した後、Java 仮想マシンのメモリ モデルを一般的に理解し、これらの仮想マシンのメモリ内のデータのその他の詳細 (データがどのように配置されているかなど) をさらに理解します。作成方法、レイアウト方法、およびアクセス方法。最も一般的に使用される仮想マシンである HotSpot と、最も一般的に使用されるメモリ領域である Java ヒープを例として取り上げ、HotSpot 仮想マシンの Java ヒープ内のオブジェクトの作成、レイアウト、およびアクセスのプロセス全体を詳しく説明します。

1. オブジェクトの作成

Java では、 new Person(); などの通常のオブジェクトを簡単に作成できます。通常の Java オブジェクトは仮想マシンでどのように作成されるのでしょうか?

1.1 メモリ領域の確認と割り当て

Java 仮想マシンがバイトコードの新しい命令を検出すると、まずこの命令のパラメータが定数プール内のクラスのシンボリック参照を見つけられるかどうかを確認し、シンボリック参照によって表されるクラスがロードされ、解析され、ロードされているかどうかを確認します。初期化された。そうでない場合は、対応するクラスのロード プロセスを最初に実行する必要があります。この部分の詳細については後で説明します。

クラス読み込みチェックに合格した後、仮想マシンは次に、新しく生まれたオブジェクトにメモリを割り当てますオブジェクトが必要とするメモリのサイズは、クラスのロードが完了した後に完全に決定でき、オブジェクトに領域を割り当てるタスクは、実際には Java ヒープから特定のサイズのメモリ ブロックを分割することに相当します。割り当てる方法は 2 つあります。

Java ヒープ内のメモリが完全に規則的であると仮定すると、すべての使用済みメモリが一方の側に配置され、空きメモリがもう一方の側に配置され、境界点の指標としてポインタが中央に配置され、割り当てられたメモリが計算されます。これは、そのポインタをオブジェクトのサイズに等しい距離だけ自由空間の方向に移動するだけです这种分配方式称为”指针碰撞”(Bump The Pointer)
しかし、Java ヒープ内のメモリが規則的ではなく、使用済みメモリと空きメモリが相互にインターリーブされている場合、単純にポインタを衝突させる方法はなく、仮想マシンはどのメモリ ブロックが使用可能であるかを記録するリストを維持する必要があります。 、割り当て時にオブジェクト インスタンスに割り当てるのに十分な大きさのスペースをリストから見つけ、リスト上のレコードを更新します这种分配方式称为"空闲列表”(Free List)

选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。したがって、圧縮仕上げプロセスを備えた Serial や ParNew などのコレクターを使用する場合、システムが採用する割り当てアルゴリズムはシンプルで効率的なポインター衝突です。また、CMS などのスイープ アルゴリズムに基づくコレクターを使用する場合、理論的には、メモリを割り当てるには、より複雑な空きリストのみを使用してください。

使用可能なスペースを分割するときは、スレッドの安全性の問題も考慮する必要があります。

オブジェクトの作成は、仮想マシンで非常に頻繁に行われる動作です。ポインタが指す場所を変更するだけであっても、同時実行条件下ではスレッドセーフではありません。メモリがオブジェクト A に割り当てられ、ポインタがオブジェクト B 同時に、元のポインタを使用してメモリが割り当てられます。この問題を解決するには 2 つのオプションがあります。

1 つは、メモリ領域を割り当てるアクションを同期することです。実際、仮想マシンは采用CAS配上失败重试的方式更新操作のアトミック性を保証します。
もう 1 つは、メモリ割り当てアクションをスレッド (Java A の各スレッド) に従って異なる領域に分割することです。メモリの小さな部分は、 と呼ばれるヒープに事前に割り当てられます。本地线程分配缓(Thread Local Allocation Buffer,TLAB)どのスレッドがメモリを割り当てたい場合でも、メモリはそのスレッドのローカル バッファに割り当てられます。ローカル バッファが使い果たされた場合にのみ、同期ロックが必要になります。新しいバッファが割り当てられます。仮想マシンが TLAB を使用するかどうかは、-XX: +/-UseTLAB パラメーターで設定できます。

1.2 オブジェクトの初期化

メモリ割り当てが完了したら、仮想マシンは割り当てられたメモリ空間 (オブジェクト ヘッダーは含まない) をゼロ値に初期化する必要があります。TLAB が使用されている場合、この作業は TLAB の割り当て時に事前に行うこともできます。この手順により、オブジェクトのインスタンス フィールドが初期値を割り当てずに Java コード内で直接使用できるようになり、プログラムはこれらのフィールドのデータ型に対応するゼロ値にアクセスできるようになります。

次に、Java 仮想マシンは、オブジェクトのオブジェクト ヘッダー (Object Header) の初期化、オブジェクトがどのクラス インスタンスであるか、クラスのメタデータ情報の検索方法、クラスのハッシュ コードなど、オブジェクトに必要な設定を行います。オブジェクトとそのオブジェクトのGC オブジェクトのヘッダには世代年齢などの情報が置かれます。現在の仮想マシンの実行状態に応じて、オブジェクトヘッダーの設定方法も異なります。

上記の作業が完了すると、仮想マシンの観点からは新しいオブジェクトが生成されます。しかし、Java プログラムの観点から見ると、オブジェクトの作成は始まったばかりです。コンストラクター、つまり Class ファイル内の () メソッドはまだ実行されておらず、コンストラクターが実行されます。

一般的に言えば、その後新しい命令が実行され<init>()方法、プログラマの希望に従ってオブジェクトが初期化され、真に使用可能なオブジェクトが完全に構築されます。

2. オブジェクトのメモリレイアウト

HotSpot 仮想マシンでは、ヒープ メモリ内のオブジェクトのストレージは次の 3 つの部分に分割できます。

  • オブジェクトヘッダー
  • インスタンスデータ
  • 位置合わせパディング (Padding)

2.1 オブジェクトヘッダー(Header)

HotSpot 仮想マシン オブジェクトのオブジェクト ヘッダー部分には、2 つの部分の情報が含まれています。

1 つ目は、用于存储对象自身的运行时数据たとえば、ハッシュ コード (HashCode)、GC 生成経過時間、ロック ステータス フラグ、スレッドが保持するロック、偏ったスレッド ID、偏ったタイムスタンプなどです。データのこの部分の長さは 32 ~ 64 ビットです。32 ビット仮想マシンではそれぞれ 64 ビットと 64 ビットが使用されます官方称它为"Mark Word"

オブジェクトは大量の実行時データを保存する必要があり、実際には 32 ビットおよび 64 ビットのビットマップ構造で記録できる最大制限を超えていますが、オブジェクト ヘッダー内の情報は追加のストレージ コストであり、オブジェクトとは何の関係もありません。オブジェクト自体によって定義されたデータ仮想マシンのスペース効率を考慮して、Mark Word は非常に小さなスペースにできるだけ多くのデータを保存し、必要に応じて独自のストレージスペースを再利用できるように、動的定義のデータ構造として設計されています。オブジェクトの状態。

例: 32 ビット HotSpot 仮想マシンでは、オブジェクトが同期ロックによってロックされていない場合、マーク ワードの 32 ビット記憶領域のうち 25 ビットがオブジェクト ハッシュ コードの保存に使用され、4 ビットがオブジェクト ハッシュ コードの保存に使用されます。オブジェクトの世代を格納します。ロック フラグの格納に 2 ビットが使用され、1 ビットは 0 に固定されます。他の状態でのオブジェクトの格納内容は次の表に示されています。

ここに画像の説明を挿入

2 番目に、はい类型指针(つまり、オブジェクトの型メタデータへのポインタ)、Java 仮想マシンはこのポインタを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。すべての仮想マシン実装がオブジェクト データの型ポインターを保持する必要があるわけではなく、言い換えれば、オブジェクトのメタデータ情報を見つけるためにオブジェクト自体を調べる必要はありません。

さらに、オブジェクトが Java 配列の場合、仮想マシンは通常の Java のメタデータ情報を通じて Java オブジェクトのサイズを決定できるため、オブジェクト ヘッダーに配列の長さを記録するために使用されるデータが必要です。ただし、配列の長さが Indeterminate の場合、メタデータ内の情報から配列のサイズを推測することはできません。

2.2 インスタンスデータ

インスタンスデータ部は、オブジェクトが実際に格納する有効な情報であり、親クラスから継承したもの、サブクラスで定義したものなど、プログラムコード内で定義された各種フィールドの内容を記録する必要がある。この部分の格納順序は、-XX: FieldsAllocationStyleJava ソース コード内の仮想マシン割り当てポリシー パラメータ ( ) とフィールドの定義順序に影響されます。

HotSpot 仮想マシンのデフォルトの割り当て順序は、 longs/doubles、ints、shorts/chars、bytes/booleans、oops (OrdinaryObject Pointers、OOPs) です。上記のデフォルトの割り当て戦略からわかるように、同じ幅のフィールドは常に割り当てられる この前提条件が満たされると、親クラスで定義された変数がサブクラスの前に表示されます。HotSpot 仮想マシンの値+XX: CompactFields参数が true (デフォルトでは true) の場合、スペースを少し節約するために、サブクラス内のより狭い変数を親クラス変数のギャップに挿入することもできます。

2.3 アライメントパディング(パディング)

配置パディングは必ずしも存在するわけではなく、特別な意味もありません。プレースホルダーとして機能するだけです。

HotSpot 仮想マシンの自動メモリ管理システムでは、オブジェクトの開始アドレスが 8 バイトの整数倍である必要があるため、言い換えれば、オブジェクトのサイズは 8 バイトの整数倍である必要がありますオブジェクトのヘッダー部分は、正確に 8 バイトの倍数 (1 倍または 2 倍) になるように慎重に設計されているため、オブジェクト インスタンスのデータ部分が整列していない場合は、整列パディングによって完成する必要があります。

3. オブジェクトのアクセス場所

オブジェクトの作成は当然そのオブジェクトをその後使用するためのものであり、Java プログラムはスタック上の参照データを通じてヒープ上の特定のオブジェクトを操作します。

「Java 仮想マシン仕様」では、参照型はオブジェクトへの参照であることのみを規定しているため、ヒープ内のオブジェクトの特定の場所を見つけてアクセスするためにこの参照をどのように使用するかは定義されていません。アクセス方法も仮想マシンによって決まります。実装に応じて、主流のアクセス方法は主にハンドルとダイレクト ポインターを使用します。

3.1 ハンドル

ハンドルを使用してアクセスする場合、Java ヒープ内のメモリのブロックがハンドル プールとして割り当てられる場合があります。参照にはオブジェクトのハンドル アドレスが格納され、ハンドルにはオブジェクト インスタンス データと型データの特定のアドレス情報が含まれます。その構造は次のとおりです。

ここに画像の説明を挿入

3.3 ダイレクトポインタ

ダイレクト ポインタ アクセスを使用する場合、Java ヒープ内のオブジェクトのメモリ レイアウトでは、アクセス タイプ データの関連情報を配置する方法を考慮する必要があります。オブジェクト アドレスは参照に直接格納されます。オブジェクト自体にのみアクセスする場合は、もう 1 つの間接アクセスは必要ありません。オーバーヘッドの構造は次のとおりです。

ここに画像の説明を挿入

ダイレクト ポインタを使用してアクセスする最大の利点は、高速であり、1 つのポインタの位置決めにかかる時間のオーバーヘッドが節約されることですが、Java ではオブジェクトへのアクセスが非常に頻繁であるため、このようなオーバーヘッドの蓄積も非常に大きな実行コストとなります。 machine HotSpot はオブジェクト アクセスに主に 2 番目の方法を使用しており、例外もありますが、ソフトウェア開発全体の観点から、さまざまな言語やフレームワークでアクセスするためにハンドルを使用することも非常に一般的です。

参考記事:

– 知識に飢えているなら、愚かでも謙虚であれ。

おすすめ

転載: blog.csdn.net/qq_42402854/article/details/130059204