JUC-Java对象内存布局(对象头,类型指针,自填充)
1. 引入问题
有如下问题:
- 请解释一下对象的创建过程
- Class对象在堆还是在方法区中
- 对象在内存中的存储布局
- 对象头具体包括什么
- 对象怎么定位
- 对象怎么分配
- Object obj = new Object(); 在内存中占用多少个字节
2. 堆内存布局
2.1 基于分代的堆的内存布局(复习)
堆:
- 年轻代(1)
- 伊甸园区(8)
- s0(1)
- s1(1)
- 老年代(2)
(注:元空间在堆外内存中)
2.2 对象在堆中组成部分
对象在堆中主要由下面三个部分组成
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
其中对象头又分为对象标志(存储对象的基本信息)和类型指针(指向类模板)。
2.3 对象头
2.3.1 对象标志(Mark Word)
对象标志用8字节64位来存储类的一些基本信息。比如:hashcode,GC分代年龄,锁标志等
注意这里分代年龄是4位。联想之前S区的分代年龄到达了15还没被GC就会到老年区,这里为什么是15?因为底层给了4bit来计算分代年龄。这里的15 = 2 ^ 4 - 1
2.3.2 类型指针(Class Pointer)
Cat object = new Cat();
分析这一句代码各个量的存储位置:
- new Cat():先在堆空间创建实例对象Cat。
- object :局部引用变量存放在栈帧中的局部变量表中
- Cat:类模板(类元数据)存放在方法区(方法区的实现元空间放在堆外内存中)
类型指针指向他的类模板,通过这个指针来确定该实例对象属于哪个类。
2.3.3 补充:32位CPU和64位CPU区别
32位CPU表示总线的位宽位32位,通过总线来寻址,也就是能够表示2 ^ 32 = 4 * 2 ^ 30 = 4G内存。
同理,64位能够表示8G内存。
2.4 实例数据
如果对象有成员属性,这里就会有数据信息。如果没有属性,就不会有数据信息。比如boolean类型的属性占1个字节。int类型占4个字节。
2.4.1 补充:8中基本数据类型占存储空间的大小。
1 | 整数类型:byte(1字节),short(2字节), int(4字节),long(8字节) |
2.5 对齐填充
2.5.1 为什么要对齐数据?
为了CPU能够高效寻址 。
3. 分析对象占多少个字节
3.1 普通对象
Object object = new Object()占几个字节?
在64位CPU上,对象头中对象标志占8个字节,类型指针占4个字节。实例数据区无数据。对齐区12不是8的倍数了,扩充4个字节变成16。因此一共16个字节。
3.2 数组对象(多了个length)
1 | public static void main(String[] args) { |
打印的内存布局信息:
可以看到SIZE一共是24byte。
对象头:对象标志8字节。类型指针4字节。数组长度4字节。
实例数据:int 1 数据4字节。
对齐:前面一共20字节。填充4字节。使其满足8的倍数。
4. 回答开头的问题
- 请解释一下对象的创建过程
- new 申请一块空间,将属性赋值为初始值。(半初始化)
- 调用构造方法,对变量进行赋值。(初始化)
- 建立关联:将引用变量指向对象的地址。
- 对象在堆还是在方法区中
对象在堆中。对象所属类的元信息才在方法区中(比如类的方法代码,变量名,方法名,访问权限)。 - 对象在内存中的存储布局
对象创建后存放在堆空间中。一个对象存储结构包括对象头,实例数据,填充空间。对象头中的对象标志用来保存对象的基本信息,比如hascode, 分代年龄,锁标志等信息。对象头中的类型指针指向元空间中的类模板,表示当前对象所属的类。实例数据中存放对象中的属性值。填充字段用于字节对齐使对象存储空间为8的倍数。 - 对象头具体包括什么
- 对象在堆还是在方法区中
- 对象标志:hashcode,锁标志,分代年龄等
- 类型指针:指向方法区中的类。
- 数组长度(数组对象才有)
对象怎么定位
栈中的局部变量表中的引用变量存放的是对象的地址。通过引用变量能定位到对象。对象怎么分配
如果对象不可逃逸,且属于热点数据,会直接在栈上进行分配。栈上分配的对象不用GC,直接弹出就行了。
如果对象占用空间很大,直接在老年代分配。
一般对象会先在年轻代的Eden区进行分配。Object obj = new Object(); 在内存中占用多少个字节
在内存中占用16字节,开启与不开启类型指针压缩都是16字节。