✡️ 堆中对象创建&布局&访问
2022年6月9日
- Java
✡️ 堆中对象创建&布局&访问
1. HotSpot 堆中对象的创建 & 分配
- 限定:
- 普通 Java 对象
- 不包括
数组
&Class 对象
1) 类加载
- 虚拟机拿到一条
new
字节码指令后 - 去常量池中找到对应类的符号引用
- 判断符号引用所代表的的类是否已经
加载
、解析
、初始化
- 若没有,则先进行
类加载
2) 分配内存
- 为新生对象分配堆中内存
- 对象所需内存大小在类加载后就已经确定
- 两种方式:
指针碰撞
- 适用于内存规整
空闲列表
- 可用于内存不规整的情况
- 内存是否规整取决于:
- 垃圾收集器是否具有空间整理功能
- 如何保证并发下线程安全:
- CAS + 失败重试
- 本地线程分配缓冲[
TLAB
]: 每个线程在堆中预先分配一小块各自内存- 是否启用
TLAB
指令:-XX:+/- UseTLAB
- 是否启用
3) 初始化 0 值
- 将分配的内存空间,除对象头外都初始化为 0 值
- 若使用
TLAB
,也可提前至TLAB
分配时进行 - 作用:
- 保证对象的实例字段在代码中可以不赋初始值就可直接使用
4) 设置对象头
<init>()
5) 执行构造函数 <init>()
方法存在于 Class 文件中
2. HotSpot 对象的内存布局
1) 对象头
存储对象自身的运行时数据 [
Mark Word
]- 数据长度:
- 32 位虚拟机: 32 bit
- 64 位虚拟机: 64 bit
- 动态数据结构,在不同情况下存储不同数据
- 存放内容:
- 哈希码
- 分代年龄
- 锁的相关信息
- 数据长度:
类型指针
- 确定该对象是哪个类的实例
- [注:] 非所有虚拟机都会在对象上保留类型指针
- 8 字节
数组长度
- 若为数组,则还需记录数组长度
2) 实例数据
- 代码中定义的各种类型的字段内容
- 存储顺序:
- 虚拟机分配策略参数
- 默认分配顺序:
longs
/doubles
ints
shorts
/chars
bytes
/booleans
oops
- 以上分配前提下,父类变量在子类变量之前
- 若
+XX:CompactFields == true
[default == true] 则允许子类变量插入父类变量的空隙中
- 默认分配顺序:
- 源码中字段的定义顺序
- 虚拟机分配策略参数
3) 对齐填充
- 起到占位符的作用
- HotSpot 中要求对象的起始地址必须为 8 字节的整数倍
- 即,任何对象大小必须为 8 字节的整数倍
- 对象头已经是 8 字节的整数倍
3. 对象的访问定位
- 访问方式由虚拟机确定
- 两种主流方式:
- 句柄:
- 单独堆中内存部分,用于存放句柄池
- 句柄中包含实例对象数据 & 类数据各自的地址信息
- 优点:
- 在对象被移动时,只需要改变句柄中的对象实例数据指针地址
- 直接指针:
- 直接存放对象实例数据地址
- 优点:
- 只需要一次指针定位,速度更快
- HotSpot 使用的方式
- 句柄: