数组变量(引用)存栈中,数组对象(含对象头、length字段、数据体)强制存堆中;基本类型数组存值,对象数组存引用;大数组易因堆内存不连续导致OOM。

数组变量存栈里,数组对象在堆上
Java 中声明 int[] arr 这类变量时,arr 本身只是个引用,它被分配在栈上;真正存数字的连续内存块(比如 10 个 int)一定在堆里。这不是约定,是 JVM 规范强制要求——所有对象实例(包括数组)都只能在堆中创建。
常见错误现象:NullPointerException 却没初始化数组,比如只写了 int[] arr; 就直接用 arr[0] = 1。这时候 arr 是 null,栈上有变量,但没指向任何堆对象。
- 使用场景:方法内局部数组(如
void foo() { int[] buf = new int[100]; })——buf在栈,new int[100]在堆 - 参数差异:传数组给方法时,传的是引用值(栈里的地址拷贝),所以方法内修改
arr[i]会影响原数组;但若在方法里执行arr = new int[50];,不会改变调用方的arr指向 - 性能影响:栈分配快、自动回收;堆分配需 GC 管理,小数组开销可忽略,但高频创建大数组(如循环里
new int[10000])会显著增加 GC 压力
new int[n] 的底层内存布局不是“n 个 int 平铺”
JVM 不会把 n 个 int 原封不动塞进一块裸内存。实际堆中,每个数组对象包含三部分:对象头(Header)、长度字段(length)、数据体(data)。其中 length 是隐式存在的,可通过 arr.length 访问,但它占 4 字节(32 位)或 8 字节(64 位压缩指针下可能更小),且紧挨着对象头。
容易踩的坑:以为 int[] a = new int[1] 和 int[] b = new int[2] 的内存地址只差 4 字节。实际上,两者起始地址差至少是对象头 + length 字段 + 对齐填充的总和,通常远大于 4。
立即学习“Java免费学习笔记(深入)”;
- 兼容性影响:不同 JVM(HotSpot / OpenJ9)、不同 GC 算法(G1 / ZGC)、是否开启压缩指针(
-XX:+UseCompressedOops),都会改变对象头大小,进而影响数组整体内存占用 - 实操建议:别依赖数组内存地址连续性做指针运算(Java 本就不支持);需要极致紧凑布局时,考虑用
ByteBuffer.allocateDirect()配合asIntBuffer(),但代价是绕过 GC、手动管理
基本类型数组不存引用,对象数组才存引用
int[]、double[]、boolean[] 这类数组,数据体里直接存值(如 4 字节的 int);而 String[]、Object[] 存的是引用(通常是 4 或 8 字节的地址)。这是栈/堆关系之外的关键分水岭。
常见错误现象:以为 String[] s = new String[3]; 创建了 3 个 String 实例——其实只创建了数组对象,s[0] 到 s[2] 全是 null。访问 s[0].length() 会抛 NullPointerException。
- 使用场景:需要默认值时,基本类型数组天然满足(
int[]元素默认为 0),对象数组必须显式初始化(s[0] = new String("a");) - 性能影响:对象数组多一层间接寻址(先读引用,再按引用去堆里找对象),且每个元素都可能触发 GC;基本类型数组无此开销
- 参数差异:泛型不能用于基本类型数组(
List<int></int>合法但int[]不能作为泛型实参),因为泛型擦除后需要统一用Object替换,而基本类型不是对象
数组创建失败时,堆空间不足比栈溢出更常见
写 new int[Integer.MAX_VALUE] 很少导致栈溢出(栈只存一个引用),绝大多数情况是抛 OutOfMemoryError: Java heap space。因为要分配约 8GB 连续堆内存(2^31 × 4 字节),而 JVM 默认堆通常远小于此。
容易被忽略的地方:即使总堆内存够,也未必能分配成功——JVM 要求数组数据体内存必须连续。G1 GC 下大数组会被直接分配到老年代,若老年代碎片化严重,new int[1000000] 都可能失败。
- 实操建议:预估数组大小,避免硬编码超大常量;动态创建前用
Runtime.getRuntime().maxMemory()和freeMemory()做粗略检查(注意这俩值有延迟,不能精确判断) - 调试技巧:加 JVM 参数
-XX:+PrintGCDetails -Xlog:gc*(JDK 10+)观察 GC 日志,看是否频繁 Full GC 或出现 “to-space exhausted” - 替代方案:真需要海量数据时,改用
ArrayList(内部扩容策略更灵活)或分块数组(int[][]),避免单次申请过大连续内存
数组看着简单,但堆布局、内存对齐、GC 行为这些底层细节,往往在压测或 OOM 时才突然暴露。别只盯着 arr[i] 怎么写,得清楚它背后那块内存到底长什么样、谁在管它、怎么碎的。









