Java 对象布局及其组成

吃可爱长大的小学妹 提交于 2020-03-30 18:56:57

Java 对象布局及其组成

在 hotspot 虚拟机中,对象在内存中布局可以被分为三部分:对象头/实例数据/补位数据。下面一张图是一个普通 java 对象和一个数组对象的结构组成:

Java 对象组成

Hotspt 采用了 OOP-Klass 模型。 它是描述 java 对象实例的模型,可分为两部分:

  • OOP (Ordinary Object Pointer)指的是普通对象指针,它包含 MarkWord 和Klass 指针。MarkWord 用于存储当前对象运行时的一些状态数据;Klass 指针则指向 Klass,用来告诉当前指针指向的对象是什么类型,即对象是使用哪个类创建出来的
  • 之所以采用这种一分为二的对象模型,是因为 hotspot jvm 的设计者不想让每个对象中都包含一个 virtual table (虚函数表), 所以把对象模型拆成 klass 和 oop,其中 oop 不包含任何虚函数,而 klass 含有虚函数表,可以进行method dispatch

对象的模型如下:

volatile markOop _mark;  //标识运行时数据
union _metadata {
	Klass*      _klass;
	narrowKlass _compressed_klass;
} _metadata;  //klass指针

对象头

对象头主要有两部分(数组对象有三组分)组成。 Markword, Klass 指针(数组对象的话,还有一个 length)。

MarkWord

标记字主要存储对象运行时的一部分数据。主要内容有 hashcode,GC 分代年龄,锁状态标志位,线程锁标记,偏向线程ID,偏向时间戳等。MarkWord 在32位和64位虚拟机上的大小分别位32bit 和 64bit,它的最后 2 bit 是锁标志位,用来标记当前对象的状态,具体如下:

状态 标志位 存储内容
未锁定 01 对象哈希码/对象分代年龄
轻量级锁定 00 指向锁记录的指针
膨胀(重量级锁定) 10 执行重量级锁定的锁指针
GC 标记 11 空(不需要记录信息)
可偏向 01 偏向线程id, 偏向时间戳,对象分代年龄

32 位 vm 在不同状态下 Markword 结构如下:

Klass 指针(元数据指针)

Klass 主要指向对象所属的类信息(class metadata)。虚拟机使用它来确定当前对象属于哪个类。klass 包含类的元数据信息,像类的方法,常量池等。你可以把它当作 java 里的 java.lang.Class 对象

数组长度

如果这个对象是数组类对象,那么如上图右侧所示,会在对象头里额外的添加一个记录数组长度的字段

实例数据

这部分主要存储的是对象实际的数据

对齐填充

hotspot vm 的自动内存管理系统要求对象起始地址比必须位 8 字节的整数倍,即对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的整数倍,因此,当对象实例数据部分没有对齐时,此时需要对齐填充来补齐

Java 对象大小计算

首先,对象头大小的确定

  • 在32位系统中,存放 Class 的指针空间大小为 4 字节,MarkWord 为 4 字节,即对象头为 8 字节
  • 在64 位系统中,存放 Class 指针的空间大小为 8 字节, MarkWord 为 8 字节,即对象头大小为 8 字节
  • 64 位系统下,如果开启了指针压缩,存放 Class指针的空间大小为 4 字节,MarkWord 为 8 字节,即对象头为 12 字节
  • 对于对象数据结构,参照上述三个规则,额外加上一个数组长度所含用的 4 个字节,就是最终对象头的大小
  • 计算对象的大小时,静态数据是不会被考虑进去的

有很多中方法计算,这里我们使用 jol 进行计算
首先假设我们待计算的对象所属的 Class 结构如下:

public class JOLSample_10_DataModels {

    /*
     * This example shows the differences between the data models.
     *
     * First layout is the actual VM layout, the remaining three
     * are simulations. You can see the reference sizes are different,
     * depending on VM bitness or mode. The header sizes are also
     * a bit different, see subsequent examples to understand why.
     */

    public static void main(String[] args) throws Exception {
        Layouter l;

        l = new CurrentLayouter();
        System.out.println("*****  " + l);
        System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

        l = new HotSpotLayouter(new X86_32_DataModel());
        System.out.println("***** " + l);
        System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

        l = new HotSpotLayouter(new X86_64_DataModel());
        System.out.println("***** " + l);
        System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());

        l = new HotSpotLayouter(new X86_64_COOPS_DataModel());
        System.out.println("***** " + l);
        System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());
    }

    public static class A {
        Object a;
        Object b;
    }

}

其在不同环境下的内存布局如下:


参考:

  1. java 的对象结构
  2. JOLSample_10_DataModels
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!