booleanおよびboolean[]JVMのメモリレイアウト
1. 概要
この簡単な記事では、さまざまな状況でのJVMのブール値のフットプリントを確認します。
まず、JVMを調べてオブジェクトのサイズを確認します。 次に、これらのサイズの背後にある理論的根拠を理解します。
2. 設定
JVM内のオブジェクトのメモリレイアウトを検査するために、Javaオブジェクトレイアウト( JOL )を広範囲に使用します。 したがって、jol-core依存関係を追加する必要があります。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
3. オブジェクトサイズ
オブジェクトサイズの観点からVMの詳細を印刷するようにJOLに依頼する場合:
System.out.println(VM.current().details());
圧縮参照が有効になっている場合(デフォルトの動作)、次の出力が表示されます。
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
最初の数行で、VMに関する一般的な情報を確認できます。 その後、オブジェクトのサイズについて学習します。
- Java参照は4バイトを消費し、 boolean s / byte sは1バイト、 char s / short sは2バイト、 ] int s / float sは4バイトであり、最後に long s / doublesは8バイトです。
- これらのタイプは、配列要素として使用する場合でも同じ量のメモリを消費します
3.1. 圧縮された参照なし
-XXを介して圧縮参照を無効にしても:-UseCompressedOops 、ブールサイズはまったく変更されません:
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
一方、Java参照は2倍のメモリを使用しています。
したがって、最初に予想されることにもかかわらず、booleansは1ビットではなく1バイトを消費しています。
3.2. ワードティアリング
ほとんどのアーキテクチャでは、単一ビットにアトミックにアクセスする方法はありません。 そうしたいとしても、別のビットを更新しているときに、隣接するビットに書き込むことになります。
JVMの設計目標の1つは、ワードティアリングとして知られるこの現象を防ぐことです。 つまり、JVMでは、すべてのフィールドと配列要素を区別する必要があります。 1つのフィールドまたは要素の更新は、他のフィールドまたは要素の読み取りまたは更新と相互作用してはなりません。
要約すると、アドレス可能性の問題と単語のティアリングが、booleanが1ビット以上である主な理由です。
4. 通常のオブジェクトポインタ(OOP)
boolean が1バイトであることがわかったので、次の単純なクラスについて考えてみましょう。
class BooleanWrapper {
private boolean value;
}
JOLを使用してこのクラスのメモリレイアウトを検査する場合:
System.out.println(ClassLayout.parseClass(BooleanWrapper.class).toPrintable());
次に、JOLはメモリレイアウトを出力します。
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
BooleanWrapperlayoutは次のもので構成されます。
- 2つのmarkワードと1つのklassワードを含むヘッダー用の12バイト。 HotSpot JVMは、 mark ワードを使用して、GCメタデータ、IDハッシュコード、およびロック情報を格納します。 また、 klass ワードを使用して、ランタイムタイプチェックなどのクラスメタデータを格納します
- 実際のブール値の場合は1バイト
- 位置合わせのための3バイトのパディング
デフォルトでは、オブジェクト参照は8バイトで整列する必要があります。したがって、JVMは13バイトのヘッダーに3バイトを追加し、ブール値を追加して16バイトにします。
したがって、ブールフィールドは、フィールドの配置のために、より多くのメモリを消費する可能性があります。
4.1. カスタムアライメント
-XX:ObjectAlignmentInBytes = 32、を使用してアライメント値を32に変更すると、同じクラスレイアウトが次のように変更されます。
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 1 boolean BooleanWrapper.value N/A
13 19 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 19 bytes external = 19 bytes total
上記のように、JVMはオブジェクトサイズを32の倍数にするために、19バイトのパディングを追加します。
5. アレイOOP
JVMがメモリ内のブール配列をどのようにレイアウトするかを見てみましょう。
boolean[] value = new boolean[3];
System.out.println(ClassLayout.parseInstance(value).toPrintable());
これにより、インスタンスレイアウトが次のように出力されます。
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) # mark word
4 4 (object header) # mark word
8 4 (object header) # klass word
12 4 (object header) # array length
16 3 boolean [Z.<elements> # [Z means boolean array
19 5 (loss due to the next object alignment)
2つに加えてマーク言葉と1つ klass 語、
配列には3つの要素があるため、配列要素のサイズは3バイトです。 ただし、これらの3バイトには、適切なアラインメントを確保するために5つのフィールドアラインメントバイトが埋め込まれます。
配列内の各ブール要素はわずか1バイトですが、配列全体がはるかに多くのメモリを消費します。 つまり、配列サイズを計算する際に、ヘッダーとパディングのオーバーヘッドを考慮する必要があります。
6. 結論
このクイックチュートリアルでは、ブールフィールドが1バイトを消費していることを確認しました。 また、オブジェクトサイズのヘッダーとパディングのオーバーヘッドを考慮する必要があることも学びました。
詳細については、JVMソースコードのoopsセクションを確認することを強くお勧めします。 また、AlekseyShipilëvには、この分野ではるかに多くの詳細な記事があります。
いつものように、すべての例はGitHubでから入手できます。