java数组内存结构核心是引用在栈、数组本体在堆;栈中仅存地址(如0x1a2b3c),堆中存对象头、length和数据区,基本类型存数值,引用类型存引用,多引用共享同一堆内存。

Java数组的内存结构,核心就一句话:引用在栈,数组本体在堆。这不是抽象概念,而是真实发生的内存分配行为——理解它,才能避开空指针、误传参、共享修改等高频问题。
栈里只存地址,不存数据
写 int[] arr = new int[5]; 时,栈中只生成一个名为 arr 的变量,里面存的不是数字,而是一个类似 0x1a2b3c 的十六进制地址。这个地址唯一指向堆中那块真实的数组空间。
- 只声明不初始化(int[] arr;)→ 栈中有变量,但值是 null,没有地址,访问 arr[0] 或 arr.length 直接抛 NullPointerException
- 方法执行结束 → 栈帧销毁,arr 这个变量消失,但堆里的数组还在,只要还有别的引用指向它,就不会被回收
堆里才是数组的“真身”
new int[5] 在堆中开辟一块连续的 20 字节空间(每个 int 占 4 字节),所有元素默认为 0。这块空间包含三部分:
- 对象头:JVM 管理所需元信息(如哈希码、GC 分代标记)
- length 字段:数组长度,不可变,也存在堆中
- 数据区:真正存值的地方——基本类型数组(如 int[])直接放数值;引用类型数组(如 String[])则放引用(即地址),每个槽位可能指向不同字符串对象,也可能为 null
多个引用指向同一块堆内存时会发生什么
执行 int[] a = new int[3]; int[] b = a; 后,栈中 a 和 b 存的是同一个地址,它们共享堆中那一片数组内存。
- 修改 a[0] = 100,b[0] 立刻变成 100
- 但若在方法内写 a = new int[10];,只是让 a 指向新地址,b 仍指向原来的数组,原数组不受影响
- 这种共享特性解释了为什么方法参数传数组能改原内容,但重新赋值不会影响调用方
基本类型数组和引用类型数组的关键差异
两者都在堆上创建对象,但内部存储逻辑不同:
- int[] a = new int[3]; → 堆中是一块连续数值区:[0, 0, 0]
- String[] b = new String[3]; → 堆中是一块连续引用区:[null, null, null],后续 b[0] = "hello"; 只是把第一个槽位填成指向字符串对象的地址,"hello" 本身存在字符串常量池或堆中其他位置
- 复制操作如 Arrays.copyOf(b, 3) 得到的是新数组对象,但里面三个引用值跟原数组一样——这是浅拷贝,不是字符串内容的复制
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











