对象的内存布局

当在Java中使用 new 创建一个对象时,就会在JVM中创建一个 instanceOopDesc 实例对象。Foo中的localValue就是保存在这个对象当中。

image.png

我们经常说Java对象在内存中的布局分为:对象头、实例数据、对其填充。其实这3部分就是对应上面图中的 oopDesc 对象。

Java对象在内存中的布局分为:对象头、实例数据、对其填充。其实这3部分就是对应上面图中的 oopDesc 对象。

_mark和_metadata 一起组成了对象头部分:

  • Mark Word:instanceOopDesc 中的 _mark 成员,允许压缩。它用于存储对象的运行时记录信息,如哈希值、GC 分代年龄(Age)、锁状态标志(偏向锁、轻量级锁、重量级锁)、线程持有的锁、偏向线程 ID、偏向时间戳等。
  • 元数据指针:instanceOopDesc 中的 _metadata 成员,它是联合体,可以表示未压缩的 Klass 指针(_klass)和压缩的 Klass 指针。对应的 klass 指针指向一个存储类的元数据的 Klass 对象。

在对象头之后,JVM会继续填充Java对象中的具体实例数据,比如Foo中的localValue。

详情


对象头

  • MarkWord

用于存储对象自身的运行时数据
数据长度在32位和64位的虚拟机中分别为32bit和64bit,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32位、64位Bitmap结构能够记录的限度。但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成为一个固定的数据结构以便在极小的空间存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

  • 对象头中的类型指针

对象指向它的类元数据的指针
虚拟机通过这个指针来确定这个对象是哪个类的实例,并不是所有的虚拟机实现都必须在对象数据上保留类型指针(还有通过句柄的方式)。另外,如果对象是一个Java数组,拿在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定对象的大小,但是从数组的元数据中却无法确定数组的大小。

句柄:可以理解为一串数字,是一个对象的唯一标识,和对象一一对应


实例数据

对象真正存储的有限信息,也是程序代码中所定义的各种类型字段内容。
无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。这部分的存储顺序会受到虚拟机分配参数(FieldAllocationStyle)和字段在Java源码中定义顺序的影响。

HotSpot虚拟机默认分配策略

  • longs/doubles
  • ints
  • shorts/chars
  • bytes/booleans
  • oop(Ordinary Object Pointers)

从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提的条件下,在父类中定义变量会出现在子类之前。如果CompactFields参数值为true,那么子类之中较窄的变量也可能会插入到父类变量的空隙之中。


对齐填充(不必须)

对齐填充仅仅起着占位符的作用。
由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。


补充:
HotSpot这个具体的拼写方式通常指的是一个JVM实现的名字——HotSpot VM。
这个JVM最初由Longview/Animorphic实现,随着公司被Sun/JavaSoft收购而成为Sun的JVM,并于JDK 1.3.0开始成为Sun的Java SE的主要JVM。在Sun被Oracle收购后,现在HotSpot VM是Oracle的Java SE的主要JVM。
HotSpot VM得名于它得混合模式执行引擎:这个执行引擎包括解释器和自适应编译器(adaptive compiler)。

详情:RednaxelaFX


历史遗留文章……

你可能感兴趣的:(对象的内存布局)