当对象创建后,对象在其生命周期内存活于Java虚拟机的内存中,一个对象从逻辑角度看,它由成员变量和成员函数构成,从物理角度来看,对象是存储在堆中的一串二进制数。

1.对象在虚拟机内存中的布局

在HotSpot虚拟机(OracleJVM)中,对象在内存中存储的布局可以分为3个区域,分别是

● 对象头(Header)

● 实例数据(Instance Data)

● 对齐填充(Padding)

1.1 对象头

HotSpot虚拟机中的对象头包含两部分信息:

1. 第一部分用于存储对象自身的运行时数据,例如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

2. 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(这个对象所属的类)

如果对象是一个Java数组,那在对象头还得使用一块用于记录数组长度的数据(数组的元数据中无法确定数组的大小)

1.2 实例数据

实例数据存储的是程序代码中定义的各种类型的字段内容,包含父类成员变量和自身变量。这部分的存储顺序受到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。

1.3 对齐填充

这一部分并不是必然存在,仅仅是起到占位作用。

因为HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍。对象头部分是8的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为8的整数倍。


2.如何访问内存中的对象

建立对象是为了使用对象,Java程序使用栈上的reference数据来操作堆上的具体对象。

引用类型的变量中存放的是一个地址,那么根据地址类型的不同,对象有不同的访问方式,主流的方式有这两种:

2.1 句柄访问

Java堆中会划分一块叫做“句柄池”的内存空间,用于存放所有对象的地址和所有对象所属类的类信息。

引用类型的变量存放的是该对象在句柄池中的地址。访问对象时,首先需要通过引用类型的变量找到该对象的句柄,然后根据句柄中对象的地址再访问对象。

如下图所示:

2.2直接指针访问

引用类型的变量直接存放对象的地址,从而不需要句柄池,通过引用能够直接访问对象。 但对象所在的内存空间中需要额外的策略存储对象所属的类信息的地址。

如下图所示:

两种方式对比:

1. 使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动时,只会改变句柄中实例数据指针,而reference本身不需要修改。

2. 使用直接指针最大的好处是访问速度快,因为它少了一次指针定位的时间开销,而对象的访问在Java中特别频繁,这类开销积少成多也是一项可观的执行成本。

对于HotSpot虚拟机,它使用的是第二种访问方式。