【Java】JVM学習(4)

オブジェクトの割り当て

JVM でのオブジェクト作成プロセス

ここに画像の説明を挿入

オブジェクトメモリの割り当て

仮想マシンは新しい命令を検出すると、まずその命令がクラス ローダーによってロードされているかどうかを確認し、ロードされていない場合は、まず対応するクラス ロード プロセスを実行する必要があります。

クラスのロードは、JVM のランタイム データ領域にクラスをロードするプロセスです。

1)检查加载

まず、この命令のパラメータが定数プール内のクラスのシンボリック参照 (シンボリック参照: シンボリック参照は参照先をシンボルのセットで記述します) を見つけることができるかどうかを確認し、クラスがロード、解析、および初期化されているかどうかを確認します。 。

符号引用 : シンボリック参照は、参照先をシンボルのセットとして記述します。シンボル参照は任意の形式のリテラルにすることができます。JAVA がコンパイルされると、各 Java クラスはクラス ファイルにコンパイルされますが、仮想マシンはコンパイル時に参照されるクラスのアドレス (実際のアドレス) を知らないため、代わりにシンボル参照を使用します。 、クラス解析フェーズ (後続の JVM クラスのロードについては詳しく説明します) では、このシンボル参照を実アドレス ステージに変換します。

Java クラス (People クラスであると想定) がクラス ファイルにコンパイルされるとき、People クラスが Tool クラスを参照するが、People クラスはコンパイル中に参照されるクラスの実際のメモリ アドレスを知らないため、シンボリックのみが参照される場合代わりに (org.simple.Tool) を参照してください。クラスローダーが People クラスをロードすると、この時点で Tool クラスの実際のメモリ アドレスが仮想マシンを介して取得できるため、シンボル org.simple.Tool を Tool クラスの実際のメモリ アドレスに置き換えることができます。

2)分配内存

その後、仮想マシンは新しく生まれたオブジェクトにメモリを割り当てます。オブジェクトにスペースを割り当てるタスクは、Java ヒープから特定のサイズのメモリを分割することに相当します。

指针碰撞
Java ヒープ内のメモリが完全に規則的で、すべての使用済みメモリが一方の側に配置され、空きメモリがもう一方の側に配置され、境界点を示すポインタが中央に配置される場合、割り当てられたメモリは次のようになります。そのポインターをオブジェクトのサイズと同じ距離だけ空き領域に移動する、この割り当て方法は「ポインター衝突」と呼ばれます。

ここに画像の説明を挿入
空闲列表
Java ヒープ内のメモリが規則的ではなく、使用済みメモリと空きメモリがインターリーブされている場合、ポインタを単純に衝突させる方法はありません。仮想マシンは、使用可能なメモリ ブロックを記録するリストを維持する必要があります。割り当てるときは、リストから十分な大きさの領域をオブジェクト インスタンスに割り当て、リスト上のレコードを更新します。この割り当て方法は「フリー リスト」と呼ばれます。
ここに画像の説明を挿入
どの配置方法を選択するかは、Java ヒープが正規かどうかによって決まり、Java ヒープが正規かどうかは、使用するガベージ コレクターがコンパクション機能を持っているかどうかによって決まります。

Serial や ParNew などの圧縮仕上げを備えたガベージ コレクターの場合、システムはポインター衝突を使用するため、シンプルで効率的です。

CMS などの圧縮 (編成) を行わないガベージ コレクターを使用する場合、理論上はより複雑なフリー リストしか使用できません。

并发安全
利用可能な領域をどのように分割するかに加えて、考慮する必要がある別の問題があります。オブジェクトの作成は、仮想マシン内で非常に頻繁に行われる動作です。ポインタが指す場所を変更するだけであっても、スレッドセーフではありません。同時実行条件では、メモリがオブジェクト A に割り当てられ、ポインタを変更する時間がなく、オブジェクト B が元のポインタを使用して同時にメモリを割り当てることが発生する可能性があります。

CAS机制
この問題には 2 つの解決策があります。1 つはメモリ領域を割り当てるアクションを同期することです。実際、仮想マシンは失敗した再試行で CAS を使用して、更新操作のアトミック性を確保します。

ここに画像の説明を挿入

CAS(Compare And Swap)ロックフリー アルゴリズムを実装するために同時プログラミングで使用されるメカニズムです。JVM では、CAS は、複数のスレッドが共有変数を同時に読み取り、比較、更新するときに一貫性を確保するために使用されるアトミック操作です。

CAS 操作は、現在の値を比較し、等しい場合は更新し、そうでない場合は再試行するという 3 つのステップで構成されます。このプロセスはアトミックであり、他のスレッドからの干渉によって中断されることはありません。CAS 操作は通常、カウンターやフラグの更新など、同時環境での共有変数に対するアトミック操作を使用します。

JVM では、CAS 操作は基礎となるハードウェアのサポートを利用して実装されます。これは、プロセッサによって提供されるアトミック命令 (比較およびスワップ命令など) を使用し、特定の CPU 命令レベルでアトミックな読み取り、比較、および更新操作を実装できます。複数のスレッドが CAS 操作を同時に実行する場合、更新操作を正常に実行できるのは 1 つのスレッドだけであり、他のスレッドは再試行するか、他の戦略を採用する必要があります。

CAS の利点は、ロックの使用によるオーバーヘッドを回避し、同時実行パフォーマンスを向上させ、デッドロックのリスクがないことです。ただし、CAS にもいくつかの問題があります。CAS 操作は現在の値に基づいて比較されるため、同時実行性が高い状況では、複数のスレッドが同時に CAS 操作を実行し、同じ共有変数を競合すると、大量の再試行とスピンが発生し、効率が低下する可能性があります。

CAS 操作の ABA 問題 (値を比較する過程で、共有変数が他のスレッドによって同じ値に変更され、誤った判断が生じる可能性がある) を解決するために、JVM は java.util.concurrent の下にアトミック クラスを提供します。 AtomicInteger、AtomicLong などのアトミック パッケージでは、CAS 操作のカプセル化が提供され、アトミック操作をより簡単に実行できます。

分配缓冲

もう 1 つは、スレッドに応じてメモリ割り当てを異なる領域に分割することです。つまり、各スレッドは Java ヒープ内の小さなプライベート メモリ、つまりローカル スレッド割り当てバッファ (スレッド ローカル割り当てバッファ、TLAB) を事前に割り当てます。 ) 、スレッドが初期化されるとき、JVM は現在のスレッドによってのみ使用される指定されたサイズのメモリの一部も適用します。そのため、各スレッドは個別のバッファを持ちます。メモリを割り当てる必要がある場合は、メモリが割り当てられます。独自のバッファ上にブロックを確保することで競合が発生することがなくなり、確保効率が大幅に向上します バッファ容量が足りない場合は、再度エデンエリアからブロックを申請して継続して使用してください。

TLAB の目的は、新しいオブジェクトにメモリ領域を割り当てるときに、各 Java アプリケーション スレッドが独自の専用割り当てポインタを使用して領域を割り当てられるようにし、同期のオーバーヘッドを削減することです。

TLAB では、各スレッドがプライベート割り当てポインターを持つことのみが許可されますが、オブジェクトを格納するための基礎となるメモリ領域には依然としてすべてのスレッドがアクセスできますが、他のスレッドはこの領域に割り当てることはできません。TLAB がいっぱいになると (割り当てポインタの先頭が割り当て制限の端に達すると)、新しい TLAB が適用されます。

3)内存空间初始化

(構築方法ではないことに注意してください) メモリ割り当てが完了したら、仮想マシンは割り当てられたメモリ空間をゼロ値(int値が0、ブール値がfalseなど)に初期化する必要があります。 。この手順により、オブジェクトのインスタンス フィールドが初期値を割り当てずに Java コード内で直接使用できるようになり、プログラムはこれらのフィールドのデータ型に対応するゼロ値にアクセスできるようになります。

4)设置

次に、仮想マシンは、オブジェクトがどのクラスのインスタンスであるか、クラスのメタデータ情報を検索する方法 (Java クラスは Java ホットスポット VM 内のクラス メタデータとして表されます) など、オブジェクトに必要な設定を行う必要があります。オブジェクトのハッシュ コード、オブジェクトの GC 世代、およびその他の情報。この情報は、オブジェクトのオブジェクト ヘッダーに保存されます。

5)对象初始化

上記の作業が完了すると、仮想マシンの観点からは新しいオブジェクトが生成されますが、Java プログラムの観点からはオブジェクトの作成が始まったばかりで、すべてのフィールドはまだ 0 です。したがって、一般に、新しい命令が実行された後、プログラマの希望に従ってオブジェクトが初期化(構築)され、真に使用可能なオブジェクトが完全に生成されます。

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

ここに画像の説明を挿入

HotSpot 仮想マシンでは、メモリに格納されているオブジェクトのレイアウトは、オブジェクト ヘッダー (Header)、インスタンス データ (Instance Data)、および位置合わせパディング (Padding) の 3 つの領域に分割できます。

オブジェクト ヘッダーには 2 つの情報が含まれており、最初の部分は、ハッシュ コード (HashCode)、GC 生成経過時間、ロック ステータス フラグ、スレッドが保持しているロック、バイアスされたスレッド ID、偏ったタイムスタンプなど。

オブジェクト ヘッダーの他の部分は、クラス メタデータへのオブジェクトのポインタであるタイプ ポインタであり、仮想マシンはこのポインタを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。

オブジェクトが Java 配列の場合、オブジェクト ヘッダーに配列の長さを記録するために使用される別のデータがあります。

位置合わせパディングの 3 番目の部分は必ずしも存在するわけではなく、特別な意味もありません。単にプレースホルダーとして機能します。HotSpot VM の自動メモリ管理システムでは、オブジェクトのサイズが 8 バイトの整数倍である必要があるためです。オブジェクトの他のデータ部分が整列していない場合は、整列パディングによって補完する必要があります。

对象的访问定位
オブジェクトを作成する目的はオブジェクトを使用することであり、Java プログラムはスタック上の参照データを通じてヒープ上の特定のオブジェクトを操作する必要があります。現在、ハンドルとダイレクト ポインタを使用する 2 つのアクセス方法が主流です。

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

ハンドルを使用してアクセスする最大の利点は、参照に安定したハンドル アドレスが格納されることです。オブジェクトが移動されるとき (ガベージ コレクション中にオブジェクトを移動することは非常に一般的な動作です)、ハンドル内のインスタンス データ ポインタのみが変更されます。リファレンス自体は改訂する必要はありません。

直接指针
ダイレクト ポインターを使用してアクセスする場合、参照に格納されるのはオブジェクトのアドレスです。

これら 2 つのオブジェクト アクセス メソッドには、それぞれ独自の利点があります。直接ポインタ アクセスを使用する最大の利点は、速度が速く、ポインタの位置決めにかかる時間コストが節約されることです。Java ではオブジェクト アクセスが非常に頻繁であるため、この種のオーバーヘッドが蓄積されます。導入コストも非常に高額です。

Sun HotSpot の場合、オブジェクト アクセスに直接ポインタ アクセスを使用します。

おすすめ

転載: blog.csdn.net/qq_43358469/article/details/131391502