文末有彩蛋!!!!!!
对象中的数据
前两篇,我们讲到了Java对象的类加载,Java对象的初始化操作。本篇,我们来继续学习Java对象,看看Java对象在内存中如何布局,看看Java对象中由哪些数据构成,以及教给大家如何测量一个对象的大小。
HotSpot虚拟机下,一个对象在内存中包含了3大区域,分别为:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头(Header)
对象头,顾名思义就是对象的头部。如果按照一个团队来看,对象头就好比团队中的领导。对于一个团队来说,领导至关重要。对于一个对象来说,对象头不可或缺。
在计算机领域中,很多知识概念都有头的存在,例如:Http请求头。
在HotSpot虚拟机中,对象头包括两部分:Mark Word和类型指针。
那么,什么是Mark Word呢?什么是类型指针呢?
(1)Mark Word
Mark Word直译为标记字段,主要用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
在不同的运行环境中,所占用内存的大小也不尽相同。在32位的机器中,Mark Word大小为32bit,4字节。在64位机器中,Mark Word大小为64bit,也就是8字节。
考虑到虚拟机的存储空间,Mark Word被设计成一个非固定的数据结构以便在最小的内存中存储更多的有用信息。
对于Mark Work来说,在不同场景下存储着不一样的信息。例如:在32位计算机中,如果一个对象处于未锁定(没有被加锁)状态下,那么该对象的Mard Word大小为32bit,其中25bit用户存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0。
而其他场景如下:
场景 | 存储内容 | 锁标志位 |
---|---|---|
未加锁 | 对象哈希码、对象分代年龄 | 01 |
轻量级锁定 | 指向锁记录的指针 | 00 |
重量级锁定 | 指向重量级锁的指针 | 10 |
GC标记 | 不需要记录任何信息 | 11 |
偏向锁 | 偏向线程ID、偏向时间戳、对象分代年龄 | 01 |
可以看出,在不同场景下,Mark Word中存储的数据是不一样的。要注意的是,当一个对象从未加锁状态转向其他状态时,原有的存储内容并不会丢失、被覆盖,而是在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝。
对于想要了解锁机制的朋友,首先需要对Mark Word有所了解,锁的实现离不开Mark Word。关于锁的内容,会在后面的多线程中进行讲解。
(2)类型指针
类型指针:对象指向其类元数据的指针,虚拟机通过这个指针确定该对象是哪个类的实例。
值得注意的是,如果对象是一个Java数组,那在对象头中还必须有一块区域用于记录数组的长度,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
实例数据(Instance Data)
实例数据部分,存储着对象真正的有效信息,也就是代码中定义的各种类型字段内容。无论是父类继承下来的,还是子类中自己定义的,都会在实例数据中保存起来。
实例数据的保存顺序,跟代码中定义的顺序有较大的关系,通常情况下父类中定义的变量会排在子类之前。
原生类型(primitive type)的内存占用如下:
原生类型 | 占用内存大小(字节) |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
reference | 4/8 |
对齐填充(Padding)
对齐填充,主要起到补全的作用。在HotSpot VM中,要求对象的大小必须是8字节的整数倍,当对象头+实例数据的大小不满足此要求时,就用到对齐填充来进行补全。
京东购买链接:可伸缩服务架构-框架与中间件