Java一维数组对象本体一定在堆内存中,栈中只存其地址;堆中结构含对象头、length字段和连续数据体,支持O(1)随机访问但长度固定,赋值为地址共享,初始化方式不影响底层布局。

Java 一维数组的对象本体一定在堆内存中,栈里只存它的地址——这是理解其行为的核心前提。
数组对象在堆中是完整、连续、带元数据的结构
当你写 int[] arr = new int[5];,JVM 在堆中分配的不是“5个int裸数据”,而是一个有组织的对象,包含三部分:
- 对象头(Header):存储哈希码、GC分代信息、锁状态等,大小依赖JVM实现和参数(如是否开启压缩指针)
- length 字段:4字节(或8字节),紧挨对象头存放,arr.length 就是直接读这个字段,不计算也不遍历
- 数据体:5个连续的 int 值(共20字节),起始地址就是整个数组对象的地址
这种布局保证了下标访问可直接用公式 首地址 + 索引 × 4 定位,无需查表或跳转。
栈中变量只是“门牌号”,不是数据本身
int[] arr; 这行代码只在栈上创建一个引用变量,初始值为 null。它不指向任何堆空间,此时若访问 arr[0] 会立即抛 NullPointerException。
立即学习“Java免费学习笔记(深入)”;
只有执行 arr = new int[5]; 后,栈中 arr 才被填入堆中那块连续内存的首地址(比如 0x7f8a…)。后续所有 arr[i] 操作,都是靠这个地址去堆里算位置。
赋值 int[] b = arr; 实际是把栈中那个地址值拷贝一份给 b,两个变量指向同一块堆内存——改 b[2] 就等于改 arr[2]。
连续性带来高效,也带来硬约束
堆中数据体严格连续,让随机访问稳定 O(1),但代价明确:
- 长度固定:new int[5] 分配的是 20 字节不可伸缩的块,扩容必须新建更大数组、复制、丢弃旧对象
- 越界受控:即使内存物理连续,arr[5] 仍会触发运行时检查,抛 ArrayIndexOutOfBoundsException
- 易受碎片影响:申请 new int[100_000_000] 时,哪怕堆总空闲够,也可能因找不到 400MB 连续空间而 OOM
基本类型数组(如 int[]、double[])的数据体直接存值;引用类型数组(如 String[])的数据体存的是对象引用,每个引用占 4 或 8 字节,真实 String 对象另在堆中别处分配。
初始化方式不影响堆布局本质
无论你用哪种方式初始化,最终堆中都生成同样结构的数组对象:
- 动态初始化:int[] a = new int[3]; → 堆中分配 3 个 int,默认全为 0
- 静态初始化:int[] b = {1, 2, 3}; → 效果等价于 new int[3] 后逐个赋值,堆布局完全一致
- 简写声明:int[] c; c = new int[3]; → 栈变量先 null,后指向新堆对象,无特殊处理
所有方式下,length 字段都由创建时长度决定,之后不可变;所有元素默认值由类型决定,由JVM一次性清零/置空整块数据体。


















