ホームページ  >  記事  >  Java  >  Java 仮想マシン 14: Java オブジェクト サイズ、オブジェクト メモリ レイアウト、およびロック ステータスの変更

Java 仮想マシン 14: Java オブジェクト サイズ、オブジェクト メモリ レイアウト、およびロック ステータスの変更

巴扎黑
巴扎黑オリジナル
2017-06-26 10:39:501052ブラウズ

オブジェクトは何バイトを占有しますか?

オブジェクトのサイズについては、C/C++であれば直接取得できるsizeof関数がありますが、Javaにはそのような方法がないようです。幸いなことに、Instrumentation クラスは JDK 1.5 以降に導入されました。このクラスは、オブジェクトのメモリ フットプリントを計算するメソッドを提供します。特定の Instrumentation クラスの使用方法については詳しく説明しません。Java オブジェクトのサイズを正確に測定する方法については、この記事を参照してください。

しかし、1 つの違いは、この記事ではコマンド ラインを使用して JVM パラメーターを渡してエージェントを指定していることです。ここでは Eclipse を通じて JVM パラメーターを設定します。 .jar の特定のパス。残りの部分については話しません。テスト コードを見てみましょう:

 1 public class JVMSizeofTest { 2  3     @Test 4     public void testSize() { 5         System.out.println("Object对象的大小:" + JVMSizeof.sizeOf(new Object()) + "字节"); 6         System.out.println("字符a的大小:" + JVMSizeof.sizeOf('a') + "字节"); 7         System.out.println("整型1的大小:" + JVMSizeof.sizeOf(new Integer(1)) + "字节"); 8         System.out.println("字符串aaaaa的大小:" + JVMSizeof.sizeOf(new String("aaaaa")) + "字节"); 9         System.out.println("char型数组(长度为1)的大小:" + JVMSizeof.sizeOf(new char[1]) + "字节");10     }11     12 }
実行結果は次のとおりです:

Object对象的大小:16字节
字符a的大小:16字节
整型1的大小:16字节
字符串aaaaa的大小:24字节
char型数组(长度为1)的大小:24字节

その後、コードは変更されず、仮想マシン パラメータ "- XX:-UseCompressedOops" が追加されます。テスト クラスを再度実行すると、結果は次のようになります:

Object对象的大小:16字节
字符a的大小:24字节
整型1的大小:24字节
字符串aaaaa的大小:32字节
char型数组(长度为1)的大小:32字节

その理由は後ほど詳しく説明します。

Javaオブジェクトのサイズ計算方法

JVMは通常のオブジェクトと配列オブジェクトのサイズを異なる方法で計算します:

説明を見てみましょう。各部分:

  1. Mark Word: オブジェクトが格納されるときに情報を記録し、占有されるメモリ サイズはマシンの桁数と同じです。つまり、 32 ビット マシンは 4 バイト、64 バイトを占有します。 -bit マシンは 8 バイトを占有します。

  2. メタデータ ポインター: 型を記述する Klass オブジェクト (Java クラスの C++ 対応物) へのポインター。Klass オブジェクトには、インスタンスが属する型のメタデータが含まれます。オブジェクトが属するため、このフィールドはメタデータ ポインターと呼ばれます。JVM は実行時にメソッド領域にある型情報を見つけるためにこのポインターを頻繁に使用します。このデータのサイズについては後で説明します

  3. 配列の長さ: 配列オブジェクトに固有の、配列の長さを記述するために使用される int 型を指す参照型。このデータのサイズは、メタデータ ポインターのサイズ (これについても後述します)

  4. インスタンス データ: インスタンス データは、8 つの基本データ型 byte、short、int、long、float、double、char、boolean (オブジェクト タイプもこれら 8 つの基本データ タイプで構成されます)、各データ タイプが占めるバイト数は 1 つずつリストされていません

  5. Padding: 変数、HotSpot のアライメントは 8 バイト アライメント、つまり、オブジェクトは 8 バイトの整数倍でなければなりません。そのため、最後のデータ サイズが 17 の場合は 7 で埋められ、前のデータ サイズが 18 の場合は 6 で埋められ、以下同様になります

最後に、メタデータ ポインターのサイズについて説明します。メタデータ ポインターは参照型であるため、通常、64 ビット マシンのメタデータ ポインターは 8 バイト、32 ビット マシンのメタデータ ポインターは 4 バイトである必要がありますが、HotSpot ではメタデータ タイプのストレージを圧縮する最適化が行われています。 、JVMパラメーターを使用します:

  • -xx:+usecompressedoops compressionをオンにしてcompressionをオンにします。メタデータ ポインターの圧縮について 圧縮が有効になっている場合、メタデータ ポインターは 64 ビット マシンで 4 バイトを占有します。 言い換えると

  • 、圧縮がオンになっている場合、64 ビットマシン上の参照は 4 バイトを占有し、それ以外の場合は通常の 8 バイトになります

Javaオブジェクトのメモリサイズの計算上記の理論的根拠により、JVMSizeofTestクラスの実行結果と、同じパラメータが「-XX:-UseCompressedOops」の後に追加される理由を分析できます。オブジェクトのサイズは異なります。

1 つ目は Object オブジェクトのサイズです。

ポインター圧縮がオンの場合、12 バイトは倍数ではないため、8 バイトの Mark Word + 4 バイトのメタデータ ポインター = 12 バイトになります。 8、4 バイト埋め込まれ、オブジェクト Object は 16 バイトのメモリを占有します

ポインタ圧縮がオフの場合、8 バイト Mark Word + 8 バイトのメタデータ ポインタ = 16 バイトです。16 バイトは正確に次の倍数であるためです。 8 なので、埋め込みバイトは必要ありません。オブジェクト Object は 16 バイトのメモリを占有します

  1. その後、文字 'a' のサイズ:

  2. ポインター圧縮がオンになっている場合、8 バイト Mark Word + 4 バイトのメタデータ ポインター + 1 バイトの文字 = 13 バイト、13 バイトは 8 の倍数ではないため、3 バイトが埋め込まれ、文字 'a' は 16 バイトのメモリを占有します

  3. ポインター圧縮がオフの場合、8 バイトのマーク Word + 8 バイトのメタデータ ポインター + 1 バイトの文字 = 17 バイト、17 バイトは 8 の倍数ではないため、7 バイトが埋め込まれ、文字 'a' は 24 を占めますメモリのバイト数

次に、整数 1 のサイズ:

  1. ポインター圧縮がオンの場合、8 バイトのマーク Word + 4 バイトのメタデータ ポインター + 4 バイトの int = 16 ワード セクション、16 バイトはちょうど 8 の倍数なので、バイトをパディングする必要はありません

  2. ポインター圧縮がオフの場合、8 バイトの Mark Word + 8 バイトのメタデータ ポインターになります。 + 4 バイト int = 20 バイト、20 バイトは正確に 8 の倍数であるため、4 バイトが埋められ、整数 1 は 24 バイトのメモリを占有します

次に、文字列「aaaaa」のサイズがあります。 , すべての静的フィールドを管理する必要はなく、String オブジェクトのインスタンス フィールドには「char value[]」と「int hash」が含まれていることがわかります:

  1. ポインター圧縮がオンになっている場合、8 バイトのマークワード + 4 バイトのメタデータポインター + 4 バイトの参照 + 4 バイトの int = 20 バイトになります。20 バイトは 8 の倍数ではないため、4 バイトが埋め込まれ、文字列「aaaaa」は 24 バイトのメモリを占有します

  2. ポインター圧縮がオフになっている場合、8 バイトの Mark Word + 8 バイトのメタデータ ポインター + 8 バイトの参照 + 4 バイト int = 28 バイト (28 バイトは次の倍数ではないため) 8 なので 4 バイトが埋め込まれます。文字列「aaaaa」は 32 バイトのメモリを占有します

最後に、長さ 1 の char 配列のサイズ:

  1. ポインタ圧縮がオンになっている場合、8 バイトのマークワード + 4 バイトのメタデータポインタ + 4 バイトの配列サイズ参照 + 1 バイトの char = 17 バイト、17 バイトは 8 の倍数ではないため、7 バイトが埋め込まれ、長さ 1 の char 配列は 24 バイトを占有します。メモリ

  2. ポインタ圧縮がオフの場合、8 バイトのマーク ワード + 8 バイトのメタデータ ポインタ + 8 バイトの配列サイズ参照 + 1 バイトの char = 25 バイトになります。25 バイトは次の倍数ではないためです。 8、したがって、7 バイトを埋めると、長さ 1 の char 配列は 32 バイトのメモリを占有します

Mark Word

Mark Word は以前にも見たことがありますが、これは、 Javaオブジェクトヘッダーの重要な部分。 Mark Word は、ハッシュ コード (HashCode)、GC 生成経過時間、ロック ステータスの識別、スレッドが保持するロック、偏ったスレッド ID、偏ったタイムスタンプなど、オブジェクト自体の実行データを保存します。

ただし、オブジェクトは大量の実行時データを保存する必要があるため、実際には 32 ビットおよび 64 ビットのビットマップ構造が記録できる制限を超えています。ただし、オブジェクト ヘッダーは追加のストレージ コストではありません。仮想マシンのスペース効率を考慮して、Mark Word はできるだけ多くの情報を非常に多くの情報を保存できるように非固定データ構造 として設計されています。小さなスペース。たとえば、32 ビット HotSpot 仮想マシン内のオブジェクトがロックされていない場合、Mark Word の 32 ビット領域の 25 ビットがオブジェクト ハッシュ コード (HashCode) の格納に使用され、4 ビットがオブジェクトの生成経過の格納に使用されます。 、および 2 ビットはオブジェクト生成年齢を格納するために使用され、1 ビットは固定ビット 0 です。その他の状態(軽量ロック、重量ロック、GCマーク、バイアス可能)のオブジェクトの格納内容は下図の通りです。

ここで特に注意すべきはロックの状態、これについては後で説明します。ロック ステータスとロック ステータスの変化を調べます。

ロックのアップグレード

上の図に示すように、ロックなし、偏ったロック、軽量ロック、重量ロックの 4 つのロック状態があり、そのうち偏ったロックと軽量ロックは で導入されました。 JDK 1.6 により、ロックの取得と解放によるパフォーマンスの消費が軽減されます。

4 つのロックのステータスは、競争に応じて徐々にエスカレートします。ロックはアップグレードできますが、ダウングレードすることはできません。つまり、偏ったロックを軽量のロックにアップグレードすることはできますが、軽量のロックを偏ったロックにダウングレードすることはできません。ロックの取得と解放の効率が向上します。図を使用してこの関係を表します:

偏ったロック

HotSpot の作成者は、以前の研究を通じて、ほとんどの場合、ロックはマルチスレッドの競合がないだけでなく、常に同じスレッドによって複数回取得されることを発見しました。スレッドが取得できるようにするため、ロックのコードが低くなるため、偏ったロックが導入されます。バイアス ロックを取得するプロセスは次のとおりです:

  1. Mark Word にアクセスして、バイアス ロック フラグが 1 に設定されているかどうか、およびフラグ ビットが 01 であるかどうかを確認します---バイアス可能な状態であることを確認します

  2. 可能であれば、バイアスされた状態で、スレッド ID が現在のスレッドを指しているかどうかをテストします。そうであれば (5) を実行し、それ以外の場合は (3) を実行します。現在のスレッドは、CAS 操作を通じてロックを競合します。競合が成功した場合は、Mark Word のスレッド ID を現在のスレッド ID に設定し、(5) を実行します。競合が失敗した場合は、(4) を実行します。

  3. CAS がバイアス ロックの取得に失敗した場合は、競争があることを意味します。グローバル セーフポイント (セーフポイント) に到達すると、バイアスされたロックを取得したスレッドは一時停止され、バイアスされたロックは軽量ロックにアップグレードされます (バイアスされたロックは競合がないと想定されているためですが、ここでは競合が存在し、バイアスされたロックはアップグレードされる)、その後、安全なポイントでブロックされたスレッドは同期コード

  4. を実行し続け、同期コード

  5. を取得するとリリースが発生します。バイアスされたロックは上記のステップ (4) にあります。

    他のスレッドがバイアス ロックをめぐって競合しようとした場合にのみ、バイアス ロックを保持しているスレッドがロックを解放します
。スレッドは積極的にバイアス ロックを解放しません。バイアスされたロックの解放プロセスは次のとおりです:

グローバル セーフティ ポイントを待つ必要があります (この時点ではバイトコードは実行されていません)

  1. まず、バイアスロックをかけてロックオブジェクトを判定 ロック状態かどうか

  2. バイアスロック解除後はアンロック(識別ビットが01)または軽量ロック(識別ビットが00)の状態に戻ります

  3. 軽量ロック

軽量ロックのロックプロセスは次のとおりです:

コードが同期ブロックに入ると、同期オブジェクトのロックステータスがロックフリーの場合、JVM はまず、現在のスレッドのスタック フレームに確立します。ロック レコードと呼ばれる空間は、ロック オブジェクトの現在のマーク ワードのコピーを保存するために使用されます。このとき、スレッド スタックのステータスは正式には Displaced Mark Word と呼ばれます。オブジェクト ヘッダーは図に示すとおりです

  1. オブジェクト ヘッダーのマーク ワードをロック レコードにコピーします

  2. コピーが成功した後、JVM は CAS 操作を使用して次のことを試みますオブジェクトのマーク ワードをロック レコードへのポインタに更新し、ロック レコード内の所有者を変更します。ポインタがオブジェクト マーク ワードを指している場合は、手順 (4) を実行します。更新が成功した場合は、手順 (5) を実行します。

  3. 更新アクションが成功した場合、現在のスレッドがオブジェクトのロックを所有し、オブジェクトの Mark Word ロック フラグ ビットが 00 に設定されます。これは、オブジェクトが軽量ロック状態にあることを意味します。このとき、行スタックとオブジェクト ヘッダーのステータスは図に示すとおりです。更新アクションが失敗すると、JVM はまずオブジェクトの Mark が現在のスレッドのスタック フレームを指しているかどうかを確認します。つまり、現在のスレッドがすでにこのオブジェクトのロックを所有しており、同期ブロックに直接入って実行を継続できることを意味します。それ以外の場合は、複数のスレッドがロックをめぐって競合することを意味し、軽量ロックは重量ロックに拡張され、ロック識別子のステータス値は 10 になります。Mark Word に格納されるのは重量ロックへのポインタであり、ロックを待っているスレッドも後でブロック状態になります。現在のスレッドは、スピンを使用してロックを取得しようとしています。スピンは、スレッドがブロックされるのを防ぐために、ループを使用してロックを取得します

  4. バイアスされたロック、軽量のロック、および重量のあるロックの比較

    以下は、バイアスされたロック、軽量なロック、および重量のあるロックを比較するための表です。オンラインで見ましたが、よく書かれていると思います。記憶を深めるためにもう一度手書きします:

以上がJava 仮想マシン 14: Java オブジェクト サイズ、オブジェクト メモリ レイアウト、およびロック ステータスの変更の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。