问题
我们现在new处理下面这样一个对象,需要分配多大的存储空间呢?
class Example {
byte b; // 1字节
int i; // 4字节
long l; // 8字节
}
是不是想当然1+4+8呢? 显然是不对的,在这之前我们先介绍下对象的内存布局和内存对齐的概念
一、内存对齐的定义与目的
内存对齐(Memory Alignment)
是 JVM 在分配对象内存时,通过 填充字节(Padding)
使数据按特定字节边界对齐的技术。
其核心目的是:
- 提高
内存访问效率
:CPU 通常以固定大小(如 8 字节)读取内存块,对齐后减少跨边界访问的指令次数 - 优化
硬件兼容性
:某些硬件平台无法访问未对齐的内存地址,对齐避免抛出硬件异常 - 减少
伪共享(False Sharing)
:对齐填充可防止多个线程访问同一缓存行的不同数据,降低并发竞争
二、Java 对象的内存布局
Java 对象在内存中分为三部分
对象头(Header)
Mark Word
:存储哈希码、GC 分代年龄、锁状态等运行时数据(32 位系统占 4B,64 位占 8B)。类型指针(Class Pointer)
:指向类元数据(默认 8B,开启指针压缩
-XX:+UseCompressedOops 后为 4B)数组长度
(可选):数组对象额外包含 4B 长度字段
指针压缩
32 位内最多可以表示 4GB,64 位地址为 堆的基地址 + 偏移量,当堆内存 < 32GB 时候,在压缩过程中,把 偏移量 / 8 后保存到 32 位地址。在解压再把 32 位地址放大 8 倍,所以启用 CompressedOops 的条件是堆内存要在 4GB * 8=32GB 以内。
启用 CompressOops 后,会压缩的对象包括:
- 对象的全局静态变量(即类属性);
- 对象头信息:64 位系统下,原生对象头大小为 16 字节,压缩后为 12 字节;
- 对象的引用类型:64 位系统下,引用类型本身大小为 8 字节,压缩后为 4 字节;
- 对象数组类型:64 位平台下,数组类型本身大小为 24 字节,压缩后 16 字节。
实例数据(Instance Data)
包含对象的字段值,按以下规则对齐
数据类型 | 对齐字节边界 |
---|---|
byte/boolean | 1 字节 |
short/char | 2 字节 |
int/float | 4 字节 |
long/double | 8 字节 |
字段顺序优化:JVM 自动重排字段顺序以减少填充空间
对齐填充(Padding)
由于 CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好也是 L1 缓存行的大小。如果不进行内存对齐,则可能出现跨缓存行的情况,这叫做 缓存行污染。
保证对象总大小为 8 字节的整数倍(HotSpot VM 默认规则)。
示例:若对象头 + 实例数据占 17B,则填充 7B 至 24B
三、内存对齐的规则与实现
对象头对齐
对象起始地址必须对齐到 8 或 12 字节(取决于 JVM 实现)。
字段对齐
字段起始地址需是其类型大小的整数倍。
class Example {
byte b; // 地址偏移 0
int i; // 偏移 4(需跳过前 3B 填充)
long l; // 偏移 8
}
总大小:1B(byte) + 3B(填充) + 4B(int) + 8B(long) = 16B
数组对齐
数组元素按类型对齐,可通过 sun.misc.Unsafe 强制对齐(如 8 字节对齐数组基地址)。
指针压缩优化
开启 -XX:+UseCompressedOops 后,类型指针和引用字段从 8B 压缩至 4B,节省内存空间。
对象到底占用多大内存?
64位 JVM,开启指针压缩 -XX:+UseCompressedOops
1. 对象头(Header)
- Mark Word:存储哈希码、锁状态等元数据,固定 8 字节。
- 类型指针(Class Pointer):开启指针压缩后占 4 字节。
总计:8B + 4B = 12B
2. 实例数据(Instance Data)
- 字段声明顺序:byte b → int i → long l,但 JVM 可能优化字段顺序以减少填充
- 实际内存布局(假设字段被重排为 long l → int i → byte b):
- long l:8 字节(对齐到 8 字节边界,无填充)。
- int i:4 字节(对齐到 4 字节边界,无填充)。
- byte b:1 字节,后填充 3 字节以满足对象整体对齐。
总计:8B + 4B + 1B + 3B(填充)= 16B
3. 对齐填充(Padding)
- 对象总大小需为 8 字节的整数倍。
- 对象头(12B) + 实例数据(16B) = 28B → 填充 4B 至 32B
4. 最终占用内存
- 理论值:12B(对象头) + 16B(实例数据) + 4B(填充) = 32B。
所以针对最开始的问题,我们的答案是32B。
一些思考
内存对齐的优势与应用场景
优势 | 应用场景 |
---|---|
提升内存访问速度 | 高频数据访问的高性能计算(如数值计算) |
减少内存碎片 | 大规模数据处理(如缓存系统、流式计算) |
避免伪共享 | 多线程高并发场景(如线程池、分布式锁) |
兼容硬件平台 | 跨平台应用开发(如嵌入式系统) |
注意事项
- 开发者无需手动干预:JVM 自动处理对齐,手动调整字段顺序可能影响可读性且优化效果有限
- 慎用底层工具:Unsafe 类强制对齐需谨慎(如数组对齐),可能导致平台依赖或内存错误
- 监控与调优:通过 jol 工具分析对象内存布局,结合 -XX:ObjectAlignmentInBytes 调整对齐策略
总结
Java 内存对齐是 JVM 提升性能的核心机制,通过对象头、字段对齐和填充字节的智能管理,平衡内存利用率与访问效率。
开发者在设计数据结构时,可优先考虑字段类型顺序,但无需过度优化。