【架构师之路】从一个对象真实占用大小引发的Java内存对齐(Memory Alignment)的思考

在这里插入图片描述

问题

我们现在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/boolean1 字节
short/char2 字节
int/float4 字节
long/double8 字节

字段顺序优化: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 提升性能的核心机制,通过对象头、字段对齐和填充字节的智能管理,平衡内存利用率与访问效率。
开发者在设计数据结构时,可优先考虑字段类型顺序,但无需过度优化。


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值