Java数组在堆内存中逻辑连续、偏移可算,非物理绝对连续;栈存引用地址,堆存数据;改元素同步生效,换引用互不影响。

Java 数组在堆内存中是连续存储的,但这个“连续”指的是逻辑上紧密排列、偏移可计算,不是物理地址绝对连贯。真正影响你写代码的是:栈里只存地址,堆里才放数据;改元素会同步生效,换引用则互不影响。
数组对象本体在堆中连续布局
每次 new int[5] 或 String[3],JVM 都会在堆中划出一块固定大小、元素紧挨着的内存区域。这块区域包含三部分:对象头(含类型信息和GC标记)、4 字节的 length 字段、紧接着的元素数据区。比如 int[5] 的数据区就是 5 个连续的 4 字节整数,地址依次递增。
- 元素地址 = 数组首地址 + 16(对象头+length)+ 索引 × 元素字节数
- int 类型每个占 4 字节,String[] 中每个元素占 4 字节(开启压缩指针时)或 8 字节
- 连续性保证了 arr[i] 是 O(1) 访问——不用遍历,直接算出位置
栈中变量只是地址“门牌号”
声明 int[] arr; 时,什么都没发生,栈里只开一个 4 或 8 字节的空槽,值为 null。执行 arr = new int[5]; 后,这个槽才被填入堆中那块连续内存的起始地址。方法结束,栈帧销毁,这个地址就丢了;如果堆里那块内存没别的引用指向它,下次 GC 就回收。
- 多个变量赋值如 int[] a = arr; b = arr; 它们共用同一个堆地址
- 访问 a[0] 和 b[0] 实际读的是同一内存位置
- 把 arr 设为 null,只断开栈到堆的这条线,不删堆中数据
基本类型数组 vs 引用类型数组:存的东西不同
两者都在堆里连续分配,但数据区内容本质不同:
立即学习“Java免费学习笔记(深入)”;
- int[]、boolean[] 等:数据区直接存数值,比如 new int[]{1,2,3} → 堆里就是 1、2、3 这三个整数
- String[]、User[] 等:数据区存的是地址,每个元素可能是 null,也可能指向堆中另一处的 String 或 User 对象
- 所以 String[] s = new String[2]; s[0] = "hello"; 实际创建了两个堆对象:一个是长度为 2 的数组,另一个是字符串常量 "hello"
传参和赋值:为什么改元素有效,换数组无效?
Java 所有参数传递都是值传递。传数组时,传的是引用的副本——也就是那个堆地址的拷贝。因此:
- arr[i] = x; → 修改的是堆中真实位置的数据,原数组立刻可见
- arr = new int[10]; → 只让形参变量指向新地址,原调用方的变量仍指着旧数组
- int[] a = b; → a 和 b 指向同一块堆内存,通过任一变量修改元素,对方都看到变化


















