作为一名Java程序员,我们在日常工作中使用这款面向对象的编程语言时,做的最频繁的操作大概就是去创建一个个的对象了。对象的创建方式虽然有很多,可以通过new
、反射、clone
、反序列化等不同方式来创建,但最终使用时对象都要被放到内存中,那么你知道在内存中的java对象是由哪些部分组成、又是怎么存储的吗?
本文将基于代码进行实例测试,详细探讨对象在内存中的组成结构。全文目录结构如下:
-
1、对象内存结构概述
-
2、JOL 工具简介
-
3、对象头
-
4、实例数据
-
5、对齐填充字节
-
6、总结
文中代码基于 JDK 1.8.0_261,64-Bit HotSpot 运行
1、对象内存结构概述
在介绍对象在内存中的组成结构前,我们先简要回顾一个对象的创建过程:
1、jvm将对象所在的class
文件加载到方法区中
2、jvm读取main
方法入口,将main
方法入栈,执行创建对象代码
3、在main
方法的栈内存中分配对象的引用,在堆中分配内存放入创建的对象,并将栈中的引用指向堆中的对象
所以当对象在实例化完成之后,是被存放在堆内存中的,这里的对象由3部分组成,如下图所示:
对各个组成部分的功能简要进行说明:
-
对象头:对象头存储的是对象在运行时状态的相关信息、指向该对象所属类的元数据的指针,如果对象是数组对象那么还会额外存储对象的数组长度
-
实例数据:实例数据存储的是对象的真正有效数据,也就是各个属性字段的值,如果在拥有父类的情况下,还会包含父类的字段。字段的存储顺序会受到数据类型长度、以及虚拟机的分配策略的影响
-
对齐填充字节:在java对象中,需要对齐填充字节的原因是,64位的jvm中对象的大小被要求向8字节对齐,因此当对象的长度不足8字节的整数倍时,需要在对象中进行填充操作。注意图中对齐填充部分使用了虚线,这是因为填充字节并不是固定存在的部分,这点在后面计算对象大小时具体进行说明
2、JOL 工具简介
在具体开始研究对象的内存结构之前,先介绍一下我们要用到的工具,openjdk
官网提供了查看对象内存布局的工具jol (java object layout)
,可在maven
中引入坐标:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
</dependency>
在代码中使用jol
提供的方法查看jvm信息:
System.out.println(VM.current().details());
通过打印出来的信息,可以看到我们使用的是64位 jvm,并开启了指针压缩,对象默认使用8字节对齐方式。通过jol
查看对象内存布局的方法,将在后面的例子中具体展示,下面开始对象内存布局的正式学习。
3、对象头
首先看一下对象头(Object header
)的组成部分,根据普通对象和数组对象的不同,结构将会有所不同。只有当对象是数组对象才会有数组长度部分,普通对象没有该部分,如下图所示:
在对象头中mark word
占8字节,默认开启指针压缩的情况下klass pointer
占4字节,数组对象的数组长度占4字节。在了解了对象头的基础结构后,现在以一个不包含任何属性的空对象为例,查看一下它的内存布局,创建User
类:
public class User {
}
使用jol
查看对象头的内存布局:
public static void main(String[] args) {
User user=new User();
//查看对象的内存布局
System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
执行代码,查看打印信息:
-
OFFSET
:偏移地址,单位为字节 -
SIZE
:占用内存大小,单位为字节 -
TYPE
:Class
中定义的类型 -
DESCRIPTION
:类型描述,Obejct header
表示对象头,alignment
表示对齐填充 -
VALUE
:对应内存中存储的值
当前对象共占用16字节,因为8字节标记字加4字节的类型指针,不满足向8字节对齐,因此需要填充4个字节:
8B (mark word) + 4B (klass pointer) + 0B (instance data) + 4B (padding)
这样我们就通过直观的方式,了解了一个不包含属性的最简单的空对象,在内存中的基本组成是怎样的。在此基础上,我们来深入学习对象头中各个组成部分。
3.1 Mark Word 标记字
在对象头中,mark word
一共有64个bit,用于存储对象自身的运行时数据,标记对象处于以下5种状态中的某一种:
<