JAVA并发编程——Java对象内存布局和对象头

1.对象在堆内存中布局
2.对象的对象头
3.对象的实例数据
4.对象的对齐填充
5.看看Object的对象头
6.看看自定义对象的对象头
7.总结
1.对象在堆内存中布局
当我们写入这样一行代码

Object object = new Object();

的时候,我们都知道它会在我们的JVM堆->新生区->伊甸园区新建一个对象,但是我们可能只是知道这个对象在哪儿,但是对这个对象的内存结构却知之甚少,今天我们就来细说一下,JAVA对象的内存布局。
我们先来看这样一张图:
JAVA并发编程——Java对象内存布局和对象头
文章图片

java对象在内存中的布局分为以下三个部分:
1)对象头
2)实例数据
3)对齐填充
接下来我们来详细介绍以下这三个部分。
2.对象的对象头
首先是对象头,这是java对象内存布局中,最重要的一个部分,它分为了两个部分:
1)对象标记Mark Word
2)类型指针(类元信息)
首先是对象标记:
JAVA并发编程——Java对象内存布局和对象头
文章图片

默认存储的是HashCode,分代年龄,锁标志位,GC标记等信息。
这些信息是与对象自身定义无关的数据,所以MarkWork被设计成一个非固定的数据结构,以便在小的空间内能够尽量存储更多的数据。
它会根据对象的状态复用自己的存储空间,也就是说在运行期间,MarkWork里的存储对象会随着锁标志位的改变而发生变化。
类型指针(类元信息):
JAVA并发编程——Java对象内存布局和对象头
文章图片

对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
对象头的大小:
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节。
3.对象的实例数据
这个部分应该是我们每天接触的部分,我们平时写的字段都在这里,还包括父类的属性信息(如果是数组的实例部分还包括数组的长度),这部分内存按照4个字节对齐。
4.对象的对齐填充
虚拟机要求对象的起始地址必须是8字节的整数倍,填充数据不是必须存在的,如果对象头+对象的实例数据不是8的整数倍,那么对象的对齐填充就会按照8字节补充对齐。
5.看看Object的对象头
在使用java查看Object的对象头之前,我们要先引入一个工具类,这样才可以更方便地查看对象头。
org.openjdk.jol jol-core 0.9

接着我们小试一下,看看我们平时使用的Object类的对象头有几个字节:
public static void main(String[] args) {Object o = new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); }

JAVA并发编程——Java对象内存布局和对象头
文章图片

【JAVA并发编程——Java对象内存布局和对象头】发现这里的问题没有,前面我们说,对象头占16个字节,但是它这里只占12个字节?最后一排显示了丢失?所以对象头究竟是占16个字节还是12个字节呢?
答案是————都对。 因为JVM默认帮我们开启了对象头的压缩,打开这个压缩,对象头就占12位,关闭这个压缩,对象头就占16位,我们再来试一下:
我们先在JVM运行的时候,使用这个参数:-XX:-UseCompressedClassPointers 这个参数就是关闭压缩参数。
JAVA并发编程——Java对象内存布局和对象头
文章图片

然再运行,获得的结果为:
JAVA并发编程——Java对象内存布局和对象头
文章图片

6.看看自定义对象的对象头
Object的对象头看完了,我们看看自定义的对象头又有什么区别。
我们先自定义一个对象
class MyObj{private Integer id; private String name; private Integer age; private String address; private Integer status; private Date createTime; private Date updateTime; }

我们发现,对象实例数据区域明显多了很多自定义的字段,并且进行了对齐填充。
JAVA并发编程——Java对象内存布局和对象头
文章图片

7.总结
今天我们学习了JAVA对象在内存中的布局,这一部分内容是为了为讲解锁升级做铺垫。

    推荐阅读