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

この記事では、オブジェクトのメモリ レイアウトとヒープ内のオブジェクトにアクセスする方法を紹介します。

序文

Object object = new Object() この文の理解度を教えてください。一般的に言えば、JDK8 のデフォルトの状況によると、新しいオブジェクトによって占有されるメモリ領域の量。

  • オブジェクトの場所がヒープに格納されることは以前に説明しました (従来の状況: エデンの園 → S0/S1 → 古い世代)。
  • それでは、レイアウトについて話しましょう。つまり、オブジェクトの構成は何ですか? 頭と体?

image-20230128003114228.png

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

HotSpot 仮想マシンでは、ヒープ メモリ内のオブジェクトのストレージ レイアウトは、オブジェクト ヘッダー (Header)、インスタンス データ (Instance Data)、アライメント パディング (Padding) の 3 つの部分に分割できます。

1) オブジェクトヘッダー

オブジェクト ヘッダーには、データの 2 つの部分が含まれます。

  • ランタイム アリティ マーク ワード
  • 型ポインタ クラス ポインタ

オブジェクトが配列の場合、配列の長さも記録する必要があります。以下に示すように:

image-20230128003700443.png

ランタイム メタデータ マーク ワールド

  • HashCode、オブジェクトの経過時間、ロック状態フラグ、スレッドが保持するロックなどの情報を保存します。

  • 64 ビット システムでは、マーク ワードが81 バイトを占め、タイプ ポインターが81 バイト [ポインター圧縮が有効な場合は 4 バイト]、合計 16 (12) バイトを占めます。

  • 新しいオブジェクトにインスタンス データがない場合、それは 16 バイトです [デフォルトでは、ポインターの圧縮が開始されます -XX:+UseCompressedClassPointers、アラインメント パディング 4 バイト]

  • マークワードの格納構造を下図に示します。

    image-20230128004733823.png

型ポインタ クラス ポインタ

  • メソッド領域のクラス メタ情報 (オブジェクト テンプレート) を指すと、仮想マシンはこのポインターを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。

2) インスタンスデータ

  • オブジェクトが実際に保持している有効な情報、つまりクラス内で定義されている各種属性(親クラスから継承したものや自身で定義したものも含む)です。
  • インスタンス データ ストレージには特定のルールがあります。
    1. 同じ幅のフィールドは常に一緒に割り当てられます。
    2. 親クラスで定義された変数は、サブクラスの前に表示されます。
    3. CompactFields パラメータが true の場合 (デフォルトは true): サブクラスのナロー変数が親クラス変数のギャップに挿入される可能性があります

3) パディングを揃える

  • 虚拟机要求对象起始地址必须是8字节的整数倍【具体原因这里就不赘述了,读者可自行查阅相关资料】。
  • 填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐

4)JOL验证

引入JOL依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
复制代码

使用JOL查看Object对象内部细节

image-20230128011130683.png

使用JOL查看Customer对象内部细节

public class Customer {
    int id;
    boolean flag = false;
    public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new Customer()).toPrintable());
    }
}
复制代码

image-20230128011916396.png

  • 7→填充的字节数
  • 声明一个Customer的实例,只有一个对象头的实例对象,12字节(开启压缩指针)+ 4字节[int] + 1字节[boolean]=17字节,此时需要对齐填充到24字节

默认开启指针压缩的 -XX:+UseCompressedClassPointers

  • 12 + 4(对齐填充) == 一个对象16字节(不算实例数据)

手动关闭指针压缩 -XX:-UseCompressedClassPointers

  • 8 + 8 == 16字节(不算实例数据)

图示对象的内存布局

image-20230128012158496.png

对象的访问定位

创建对象是为了后续使用该对象,那么JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?

  • 通过栈上的reference访问

而reference类型只是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,而JVM中主流的对象访问方式主要有使用句柄直接指针两种方式。

1)句柄访问

Java堆中可能会划分出一块内存来作为句柄池,而reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。

  • 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时候移动对象很普遍)时会改变句柄中的实例数据指针即可,reference本身不需要被修改
  • 缺点:需要多占用一些空间

image-20230128113610266.png

2)直接指针

  • HotSpot使用该方式。

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

image-20230128114455925.png

おすすめ

転載: juejin.im/post/7193547233650802746
おすすめ