一、【核心概念/问题引入】
1.1 先搞懂核心:数组内存到底是什么?
把内存比作生活中的空间,一秒就懂了:
- 栈内存:像随身口袋,空间小但取用快,放的是临时物品(方法中的局部变量)
- 堆内存:像大型仓库,空间大但取用慢,放的是长期存储的物品(new 出来的对象)
官方定义是:Java 程序运行时,内存分为栈内存和堆内存,数组名存储在栈内存中,作为引用指向堆内存中真正的数组数据。
1.2 为什么需要理解数组内存?
- 没有理解内存:遇到空指针异常、索引越界等问题时束手无策,只能靠试错解决
- 理解了内存:能够从根本上理解数组的工作原理,轻松定位和解决内存相关问题
二 、【核心知识点详解】
2.1 Java 内存区域核心划分
Java 虚拟机(JVM)在运行时会将内存划分为不同区域,其中与数组操作最相关的是以下三个:
| 内存区域 | 作用 |
|---|---|
| 方法区 | 存储已加载的类信息、常量、静态变量等(比如 Test 类的结构信息)。 |
| 栈内存 | 每个方法执行时创建 "栈帧",存储局部变量表(基本类型、对象引用)。 |
| 堆内存 | 存储对象实例(包括数组对象,因为数组在 Java 中也是对象)。 |
2.2 一维数组:完整内存生命周期
将以这个代码为例,逐行分析代码,结合内存图解来理解变化过程。
ini
public class Test {
public static void main(String[] args) {
int[] arr = {11, 22, 33};
arr[0] = 44;
arr[1] = 55;
arr[2] = 66;
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
}
1. 类加载:Test 类进入方法区
当程序启动时,JVM 会先将 Test 类加载到方法区 ,存储类的结构信息(比如 main 方法的定义)。
此时内存状态:

2. 执行 main 方法:栈帧入栈
main 方法开始执行时,JVM 会在栈内存 中为 main 方法创建一个 "栈帧",栈帧的局部变量表用于存储方法内的变量(比如 args 和后续的 arr)
此时内存状态:

3. 执行 int[] arr = {11, 22, 33};:创建数组对象
这一步是核心,会同时涉及栈内存 和堆内存:
- 堆内存 :分配一块连续空间,创建长度为 3 的
int数组对象,并初始化元素为11, 22, 33(数组对象会包含 "长度" 信息和元素槽位)。 - 栈内存 :将堆中数组的内存地址(引用)赋值给局部变量
arr,让arr指向堆中的数组。
此时内存状态(关键变化):

4. 执行 arr[0] = 44; arr[1] = 55; arr[2] = 66;:修改数组元素
由于 arr 存储的是堆中数组的引用 ,因此通过 arr[索引] 可以直接定位到堆中数组的元素,并修改其值:
arr[0] = 44:将堆中数组索引 0 的值从11改为44。arr[1] = 55:将索引 1 的值从22改为55。arr[2] = 66:将索引 2 的值从33改为66。
此时内存状态(堆中数组元素被修改):

5. 执行 System.out.println(...):打印数组元素
通过 arr 引用找到堆中数组,取出当前索引对应的值(44, 55, 66),输出到控制台。

2.3 两个数组指向相同内存
这段代码的核心是 "引用赋值" ------ 两个数组变量 array1 和 array2 最终会指向堆内存中的同一个数组对象。我们结合内存区域逐行分析,并配上直观的图解。
ini
public class Test {
public static void main(String[] args) {
int[] array1 = {11, 22, 33};
int[] array2 = array1;
array2[0] = 100;
System.out.println(array1[0]);
System.out.println(array2[0]);
}
}
1. 类加载:Test 类进入方法区
程序启动,JVM 将 Test 类加载到方法区

2. 执行 main 方法:栈帧入栈
main 方法开始执行,在栈内存 创建栈帧,局部变量表预留 array1 和 array2 的位置(此时还未赋值)。

3. 执行 int[] array1 = {11, 22, 33};:创建第一个数组
这一步同时操作栈和堆:
- 堆内存 :分配空间,创建长度为 3 的
int数组,初始值11, 22, 33,地址假设为0x666。 - 栈内存 :将堆中数组的地址
0x666赋值给array1,让array1指向该数组。

4. 执行 int[] array2 = array1;:关键!引用赋值
这是代码的核心 ------ array2 复制了 array1 的引用(地址) ,而不是复制数组对象本身。
结果:array2 和 array1 都指向堆内存中同一个数组 (地址 0x666)。

5. 执行 array2[0] = 100;:修改数组元素
由于 array2 和 array1 指向同一个数组,通过 array2 修改元素时,堆中唯一的数组对象会被改变 。
结果:索引 0 的值从 11 变为 100。

6. 执行打印语句:输出结果
通过 array1 和 array2 访问的是同一个堆数组,因此:
System.out.println(array1[0]);→ 输出100System.out.println(array2[0]);→ 输出100

三、总结
3.1 、Java 内存区域的核心分工(基础)
| 内存区域 | 与数组相关的核心作用 |
|---|---|
| 方法区 | 存储类信息(如Test类的结构),与数组对象的具体运行时数据无关。 |
| 栈内存 | 存储局部变量表,其中的array1、 array2 等变量是数组的引引用(堆中数组的地址)。 |
| 堆内存 | 存储数组对象本身(因为数组在Java中是对象),包括数组的长度和具体元素值。 |
3.2 两段代码的核心行为总结
1. 第一段代码:单引用操作数组
- 创建数组 :
int[] arr = {11, 22, 33};→ 堆中分配空间创建数组,栈中arr引用指向堆数组地址。 - 修改元素 :
arr[0] = 44;→ 通过栈中arr的引用,直接修改堆中数组的元素值。
2. 第二段代码:多引用指向同一数组
- 引用赋值 :
int[] array2 = array1;→ 不是复制堆中的数组对象,而是复制堆数组的地址 给array2,导致array1和array2指向堆中同一个数组。 - 联动修改 :
array2[0] = 100;→ 由于两个引用指向同一堆对象,通过array2修改元素后,array1访问时也会看到变化。
3.3 必须记住的核心规则
- 数组是对象,永远在堆里 :无论代码怎么写,数组实例一定存储在堆内存中。
- 数组变量是 "引用",不是对象本身 :栈中的
arr、array1、array2只是堆中数组的 "地址指针",不是数组本身。 - 引用赋值 = 共享同一个对象 :
array2 = array1会让两个引用绑定到同一个堆数组,修改其中一个,另一个必然受影响。 - 通过引用修改的是堆中的对象 :
arr[0] = 44或array2[0] = 100,本质都是直接修改堆中数组的元素值。