一.

1.程序运行时,JVM 加载 Test 类,将类的元数据(包括静态变量 b、成员变量 c 定义,以及 main/change/eat 方法的字节码 )存入方法区

2.静态变量 b 在方法区分配内存,默认值初始化后赋值为 20 ;静态常量池(方法区的一部分 )存储 mainchange 等方法的符号引用,以及字符串字面量(如后续用到的 "demo" 先标记 )。

3.main 是静态方法,JVM 为其创建栈帧压入线程栈,开始逐行执行:

4.int a = 20;:在 main 栈帧的局部变量表,分配基本类型 int 空间,存入值 20 。

5.String res = "demo";

        字符串 "demo" 先去堆 - 字符串常量池查找,若不存在则创建,地址标记为 0x11 ;

  res 作为引用类型,在 main 栈帧局部变量表存地址 0x11 ,指向常量池的 "demo" 。

6.访问静态变量(方法区直接获取)

System.out.println(b);b 是静态变量,属于类,直接从方法区的静态变量池获取其值 20 ,输出到控制台。

7.原代码中 System.out.println(c); 被注释,因为 c 是成员变量  (属于对象,需创建 Test 对象才能访问 ),静态方法 main 无法直接访问非静态成员,若未注释会编译报错。

8.调用静态方法 change(线程栈 - 新栈帧)

change();:为 change 方法创建新栈帧压入线程栈,执行:局部变量 int num = 30;:在 change 栈帧局部变量表存 30 ;执行 System.out.println("change方法执行了"); ,输出文本后,change 栈帧弹出线程栈(方法执行完毕 )。

9.跳过 eat 方法(未创建对象时无法调用)

原代码中 eat(); 被注释,因为 eat 是非静态方法(需通过对象调用 ),静态方法 main 直接调用会编译报错。

10.创建 Test 对象(堆 - 对象实例)Test test = new Test();:                                                  在中创建 Test 对象:分配内存,成员变量 c 初始化(默认值后赋值为 30 );                          对象头标记对象类型(指向方法区 Test 类元数据 ),并为 eat 方法预留引用;                            将对象在堆中的地址(假设 0x22 )返回,test 作为引用类型,在 main 栈帧局部变量表存地址 0x22 。

11.调用对象的 eat 方法(线程栈 - 新栈帧 + 堆对象关联)                test.eat();:通过 test 引用找到堆中 Test 对象,为 eat 方法创建栈帧压入线程栈;                  执行 System.out.println("eat方法执行了"); ,输出文本后,eat 栈帧弹出线程栈。

12.访问对象的成员变量(堆 - 对象实例)System.out.println(test.c);:                                  通过 test 引用(地址 0x22 )找到堆中 Test 对象,读取成员变量 c 的值 30 ,输出到控制台。

二.

1.加载类信息: 将Test类的元数据(包括静态变量b的定义、main/change/sort方法的字节码、成员变量c的定义)加载到方法区

2.静态变量初始化:静态变量b在方法区分配内存并赋值为20,其生命周期与类一致。

3.main方法作为程序入口,JVM 为其创建栈帧并压入线程栈,开始逐行执行:

4.定义numint num = 10;main栈帧的局部变量表中分配空间,存储基本类型值10(直接存值,不涉及堆)

5.调用change方法参数传递:将num的值10复制一份作为参数a传入change方法(值传递,传递的是副本)。change栈帧操作:执行a=100;:修改change栈帧中a的值为100(仅影响当前栈帧)。输出a的值100后,change栈帧弹出线程栈(局部变量a销毁)返回main方法main栈帧中num的值仍为10(值传递不影响原变量),输出10

6.创建数组int[] arr = {5,7,4,2};中分配内存创建数组对象,存储元素[5,7,4,2],假设地址为0x001main栈帧的局部变量表存储数组引用arr,其值为堆中数组的地址0x001(引用指向堆中对象)。

7.调用sort方法参数传递:将arr的引用值0x001复制一份传入sort方法(引用传递,传递的是地址副本)。sort栈帧操作:为sort方法创建新栈帧并压入线程栈,局部变量表存储参数arr(值为0x001,与main中的arr指向同一数组)。执行arr[2] = 100;:通过引用找到堆中数组,修改索引 2 的元素为100(直接操作堆中对象)。输出修改后的数组[5,7,100,2]后,sort栈帧弹出线程栈(参数arr销毁,但堆中数组仍存在)。返回main方法main栈帧中的arr仍指向堆中数组(地址0x001),输出修改后的数组[5,7,100,2]

三.

1.JVM 分别加载 Cat 类与 Test 类,将类元数据(包括 Cat 的字段 name/weight、静态变量 country、方法 run/eat/toStringTest 的 main 方法 )存入方法区

2.静态变量初始化Cat 类的静态变量 country 在方法区的静态常量池分配内存,赋值为 "中国"(类加载时初始化,全局唯一 )。即:静态变量 country 本身(存引用的容器)在方法区,它指向的字符串常量 "中国" 在堆的常量池 。

3.main 是程序入口,JVM 为其创建栈帧压入线程栈,逐行执行代码:

4.执行 Cat cat1 = new Cat();堆内存分配:在中创建 Cat 对象(地址 0x11 ),成员变量 name(默认 null )、weight(默认 0 )初始化。引用存储main 栈帧的局部变量表中,cat1 存堆中 Cat 对象的地址 0x11 ,建立引用关联。

5.执行 cat1.name = "小花"; cat1.weight = 10;:通过 cat1 引用(地址 0x11 )找到堆中 Cat 对象,修改成员变量:name 指向字符串常量池的 "小花"(地址 0xbb ),weight 赋值 10 。

6.执行 Cat cat2 = new Cat();堆内存分配:在中创建另一个 Cat 对象(地址 0x22 ),成员变量 name(默认 null )、weight(默认 0 )初始化。引用存储main 栈帧的局部变量表中,cat2 存堆中 Cat 对象的地址 0x22 ,建立引用关联。

7.执行 cat2.name = "小黑"; cat2.weight = 18;:通过 cat2 引用(地址 0x22 )找到堆中 Cat 对象,修改成员变量:name 指向字符串常量池的 "小黑"(地址 0xcc ),weight 赋值 18 。

8.执行 System.out.println("小猫1:" + cat1);:拼接字符串时,触发 cat1 的 toString 方法:JVM 为 toString 方法创建栈帧压入线程栈,执行 return "Cat{name='" + name + "', weight=" + weight + ", country=" + country + "}" 。toString 方法中:country 取方法区 Cat 类的静态变量 "中国"(地址 0xaa )。weight 取 10name 取堆中 cat1 对象的 name(指向 0xbb,即 "小花" );拼接结果返回后,toString 栈帧弹出,输出 小猫1:Cat{name='小花', weight=10, country=中国} 。

注:这个类里所有静态的 是所有未来共享的数据,由这个模板创造出来的所有的对象会共享这个变量。非静态的,普通的他是属于每个对象内部独有的。例如,改变cat1中的name,weight只会在其内部修改,而改变静态变量country,所有对象都会改变。

change1交换不了的原因:

参数传递时:调用 change1(cat1, cat2) 时,实际是把 main 方法里 cat1cat2 的引用值(地址副本) 传给 change1 的形参 cat1cat2。假设 main 里 cat1 存地址 0x11(指向堆中 “小花” 对象 ),cat2 存地址 0x22(指向堆中 “小黑” 对象 ),那么 change1 的形参 cat1 拿到 0x11,形参 cat2 拿到 0x22

方法内交换change1 里交换的是形参变量本身cat1 和 cat2 这两个局部变量的引用值 ),和 main 里的 cat1cat2 是不同的变量(只是初始值相同 )。交换后,change1 的 cat1 存 0x22cat2 存 0x11,但这只影响 change1 方法内的局部变量。

方法结束后change1 栈帧销毁,形参 cat1cat2 消失,main 里的 cat1cat2 引用值没任何变化,仍指向原来的对象,所以交换 “不生效”。

change2 能交换的原因:

change2 里的形参 cat1cat2 虽然也是引用的 “副本”,但通过引用操作堆中对象的属性nameweight )。

比如 cat1.name = cat2.name,是用形参 cat1 的引用(地址 0x11 )找到堆中对象,直接修改对象的 name 属性。

这些修改会反映到堆中实际对象,main 里的 cat1cat2 再访问对象时,就能看到变化,所以交换 “生效”。

四.

1.加载 Test1 类:JVM 加载 Test1 类,将 main 方法等元数据存入方法区,静态常量池同步记录 main 方法的符号引用 。

2.main 方法启动,JVM 为其创建栈帧压入线程栈,逐行执行代码:

3.int[] arr = {5,7,4,2};:在中分配数组对象(地址 0x1 ),存储元素 [5,7,4,2]main 栈帧局部变量表存 arr = 0x1(引用堆数组 )。

4.String str = new String("xx");:字符串常量池先检查有无 "xx",无则创建(地址 0xa );在中创建 String 对象(地址 0x2 ),其 value 引用常量池的 "xx"main 栈帧局部变量表存 str = 0x2(引用堆中 String 对象 )。

5.String[] arr1 = {"hello","hello","str",new String(...)};:字符串常量池创建 / 复用 "hello"(地址 0xb )、"str"(地址假设 0xc ,图中未完整体现 );在中分配数组对象 arr1(地址 0x3 ),数组元素为:arr1[3]:堆中新建 String 对象(地址 0x4 ,value 引用常量池内容 );arr1[2]:引用常量池 "str"(地址 0xc );arr1[0]arr1[1]:均引用常量池 "hello"(地址 0xb );main 栈帧局部变量表存 arr1 = 0x3(引用堆中数组 )。

  • System.out.println(arr1[0] == arr1[1]);
    arr1[0] 和 arr1[1] 都引用常量池的 "hello"(地址 0xb ),地址相同,输出 true 。

  • System.out.println(arr1[0] == arr1[3]);
    arr1[0] 引用常量池 "hello"0xb ),arr1[3] 引用堆中新建 String 对象(0x4 ),地址不同,输出 false 。

  • System.out.println(arr1[2] == arr1[4]);
    arr1[2]引用常量池"str"0x02),arr1[4]引用常量池"xx"0x03),地址不同 → 输出false

1.arr2 作为 main 方法的局部变量,存储在线程栈的 main 栈帧里,值是堆中数组对象的地址(比如内存图里的 0x33 )。这一步体现 “栈存引用”,arr2 本身是个 “指针”,指向堆里的数组。

2.堆里有个专门的数组对象(地址 0x33 ),这个数组长度是 3,里面存的是 3 个 Cat 对象的引用地址arr2[0] 存 0x11 → 指向 cat1 对应的 Cat 对象;arr2[1] 存 0x22 → 指向 cat2 对应的 Cat 对象;arr2[2] 存 0x44 → 指向新创建的 Cat 对象。

3.每个 Cat 对象(地址 0x110x220x44 )都在里,包含默认属性(name=nullweight=0 )和方法引用(eat()toString() ,方法本体在方法区 )。

“引用嵌套” 的本质

数组的 “多维” 特性,本质是 **“数组对象的嵌套引用”**:

一维数组:栈存地址 → 堆存数组 → 数组存实际数据(基本类型 / 对象)。

二维数组:栈存地址 → 堆存 “外层数组” → 外层数组存 “内层数组地址” → 内层数组存实际数据

三维数组:栈存地址 → 堆存 “最外层数组” → 逐层嵌套存 “下一层数组地址” → 最终层存实际数据

Java 多维数组的内存模型,是通过 “栈引用 + 堆对象 + 多层嵌套地址关联” 实现的动态数据结构,栈负责快速定位,堆负责存储嵌套的数组对象与实际数据,充分体现了 Java 引用类型的灵活性与内存区域的分工协作。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐