🔴 Java对象在堆内存中的存储布局分为三个主要部分:对象头(Object Header)、实例数据(Instance Data)、对齐填充(Padding)。对象头是理解synchronized锁机制的关键。
🔴 对象内存布局三大结构
🔴 1. 对象头 (Object Header)
🔴 对象头是Java对象内存布局的第一部分,包含对象的元数据信息,是synchronized锁机制实现的基础。
🔴 1.1 Mark Word (标记字段)
- 🔴 锁状态标记:无锁、偏向锁、轻量级锁、重量级锁
- 🔴 HashCode:对象的哈希码
- 🔴 GC分代年龄:垃圾回收分代信息
- 🔴 偏向锁线程ID:偏向锁的线程标识
- 🔴 偏向时间戳:偏向锁的时间信息
🟠 1.2 类型指针 (Class Pointer)
- 🟠 指向方法区:指向方法区中类的元数据
- 🟠 类型检查:instanceof操作的基础
🟠 指针压缩 (Compressed Oops)
- 🟠 压缩原理:64位指针压缩为32位,减少内存占用
- 🟠 地址对齐:对象地址8字节对齐,后3位总是0
- 🟠 压缩过程:存储时右移3位去掉0,访问时左移3位恢复
- 🟠 适用范围:堆内存≤32GB时有效,>32GB时自动失效
- 🟠 JVM参数:
-XX:+UseCompressedOops
(默认开启)
🟡 句柄池 vs 直接指针
- 🟡 句柄池 (Handle Pool):
- 对象引用指向句柄池中的句柄,句柄再指向实际对象
- 优点:对象移动时只需更新句柄,引用地址不变
- 缺点:多一次间接访问,性能开销较大
- 🟡 直接指针 (Direct Pointer):
- 对象引用直接指向对象在堆中的地址
- 优点:访问速度快,减少间接寻址开销
- 缺点:对象移动时需要更新所有引用
- 🟡 HotSpot选择:使用直接指针,通过其他技术解决对象移动问题
🟢 1.3 数组长度 (Array Length)
- 🟢 仅数组对象:只有数组对象才有此字段
- 🟢 长度信息:存储数组的长度
🟢 1.4 字节序存储 (Endianness)
- 🟢 大端存储 (Big Endian):高位字节存储在低地址
- 🟢 小端存储 (Little Endian):低位字节存储在低地址
- ⚪ JVM实现:对象内存存储遵循硬件架构,但ByteBuffer等I/O操作默认使用大端存储
🔴 2. 实例数据 (Instance Data)
🔴 实例数据存储对象的实际字段值,是对象的核心数据部分。
🟢 字段存储规则
🟢 1. 字段重排序规则
- 🟢 按类型大小排序:字段按字节数从大到小排列
- 8字节:long、double
- 4字节:int、float
- 2字节:short、char
- 1字节:byte、boolean
- 🟢 引用类型后置:引用类型字段排在基本类型之后
- 🟢 父类字段在前:父类字段在子类字段之前
- 🟡 声明顺序保持:同类型字段保持声明顺序
🔴 2. FieldsAllocationStyle 策略
- 🔴 策略0:基本类型 → 填充字段 → 引用类型
- 比较“机械”的方式,不考虑类型分组或继承
- 常见于早期JVM
- 🔴 策略1:引用类型 → 基本类型 → 填充字段
- 把所有引用类型字段靠前放,优化对象访问的引用局部性
- 🔴 策略2:混合策略(默认)
- 父类部分按策略0排列
- 子类部分按策略1排列
- 将父类与子类的引用类型尽量放在一起
- 优点:考虑继承结构,减少内存碎片、提高缓存利用率、加快GC的效率
- 🟡 JVM参数:
-XX:FieldsAllocationStyle=0/1/2
- 🟡 简单总结:策略0=简单顺序,策略1=引用优先,策略2=继承优化混合排序(默认)
🔴 字段插入面试问题
高频面试题:
- Q:当父类和子类都有实例字段时,子类的字段会不会插入到父类的字段布局空隙里?
- A:不会。
- HotSpot在分配对象布局时,父类和子类的实例字段是独立布局区域,父类部分结束后才开始子类字段。
- 即使父类有未使用的填充字节,子类也不会去“填补”。
- 布局原则:父类字段区域 → 子类字段区域(独立布局)。
- 优化范围:字段插入优化仅在同一层级内生效(父类内部或子类内部)。
中频延伸:
- Q:FieldsAllocationStyle策略2的混合优化体现在哪里?
- 父类部分按策略0排列(基本类型 → 填充字段 → 引用类型)
- 子类部分按策略1排列(引用类型 → 基本类型 → 填充字段)
- 将父类与子类的引用类型尽量放在一起(但不会跨区域插入字段)
低频问法:
- Q:为什么JVM不允许子类字段利用父类的填充空间?
- 保持父类字段偏移的稳定性(反射、Unsafe、JIT 优化依赖)
- 避免类加载隔离问题(父类可能独立加载或共享)
- 设计权衡:牺牲少量内存,换取更好性能与可预测性
🟠 3. 内存对齐优化
- 🟠 减少填充:通过字段重排序减少内存浪费
- 🟠 提高缓存命中率:优化内存访问效率
- 🟠 CPU友好:减少CPU访问开销
- ⚪ JVM自动处理:开发者无需手动干预
🟠 3. 对齐填充 (Padding)
🟠 对齐填充是为了满足JVM内存对齐要求而添加的空白字节。
🟠 对齐原因
- 🟠 8字节对齐:JVM要求对象大小必须是8字节的倍数
- 🟠 性能优化:内存对齐提高CPU访问效率
- 🟡 内存浪费:可能造成少量内存浪费