java数组引用存栈、数组本体存堆;栈中仅存地址,堆中存实际数据(含对象头、length和元素),基本类型数组存数值,引用类型数组存引用。

Java数组的内存分布,核心就两点:引用在栈,数组本体在堆。理解这个映射关系,就能看懂null异常、数组共享、方法传参等常见问题的根源。
栈里只存“地址”,不存数据
当你写 int[] arr = new int[5];,栈中只生成一个叫 arr 的变量,它里面存的不是五个数字,而是一个十六进制地址(比如 0x1a2b3c)——这个地址指向堆里的真实数组。
一旦方法执行结束,arr 这个变量从栈中消失,但堆里的那块 5 个 int 的空间还留着,直到没有其他引用指向它,才被垃圾回收器清理。
常见误区:
- 只声明不初始化:int[] arr; → 栈中有 arr,但值为 null,没地址,不能访问 arr[0]
- 把 null 当数组用:System.out.println(arr.length); → 直接抛出 NullPointerException
堆里才是数组的“真身”
new int[5] 在堆中开辟连续的 20 字节空间(每个 int 占 4 字节),所有元素自动初始化为 0。这块空间有唯一地址,且内容可被多个栈变量共同访问。
关键细节:
- 基本类型数组(如 int[]、boolean[]):堆中直接存数值
- 引用类型数组(如 String[]、Object[]):堆中存的是“引用”,每个槽位是个地址,可能指向堆中不同位置的字符串对象,也可能为 null
- 数组对象本身有隐藏字段:除了数据区,还包含对象头和 length 属性,这些也存在堆中
两个变量指向同一块堆空间时会发生什么
执行 int[] a = new int[3]; int[] b = a; 后,栈中 a 和 b 都存着同一个地址,它们共用堆中那一片数组内存。
此时修改任意一个:
- a[1] = 100;→ 堆中第 2 个位置变成 100
- 再读 b[1] → 得到的就是 100,不是旧值
这解释了为什么向方法传数组后,方法内修改元素会影响原数组——传的是地址,不是副本。
方法调用中数组参数的典型行为
考虑这个方法:void change(int[] x) { x[0] = 999; x = new int[2]; }
调用 change(arr); 后:
- x[0] = 999 会改变堆中 arr 所指数组的第一个元素
- x = new int[2] 只是让栈中的局部变量 x 指向新数组,不影响原 arr 变量的值,也不影响 arr 所指的旧数组
所以方法返回后,arr[0] 是 999,但 arr.length 仍是原来的长度,不会变成 2。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











