一、代码全景解析:两个对象如何诞生
1.1 类定义核心结构
java
public class Student {
String name; // 成员变量:字符串类型
int age; // 成员变量:整型
public void study() { // 成员方法
System.out.println("好好学习");
}
}
public class Test2Student {
public static void main(String[] args) {
// 对象实例化过程
Student s1 = new Student();
s1.name = "阿强";
s1.age = 23;
s1.study();
Student s2 = new Student();
s2.name = "阿珍";
s2.age = 24;
s2.study();
}
}
1.2 执行结果特征
arduino
001 // s1对象地址
阿强...23
好好学习
002 // s2对象地址
阿珍...24
好好学习
二、内存三区深度解剖图

2.1 栈内存:方法执行的战场
- 方法调用栈帧 :
-
main()
方法栈帧-
局部变量表:
javas1 -> 0x001 // 指向堆内存地址 s2 -> 0x002
-
-
study()
方法临时栈帧(调用时创建,执行完销毁)
-
2.2 堆内存:对象生存的土壤
-
对象实例数据 :
java// 对象0x001 name = "阿强" // String类型引用 age = 23 // 基本类型直接存储 // 对象0x002 name = "阿珍" age = 24
-
方法表指针:指向方法区中Student类的元数据
2.3 方法区:类的蓝图仓库
- 类元信息存储 :
Student.class
:- 成员变量定义:
name
,age
- 成员方法字节码:
study()
- 成员变量定义:
Test2Student.class
:main()
方法字节码
三、对象创建七步拆解(以s1为例)
-
类加载检测
JVM检查方法区是否存在Student类信息,若无则加载
-
堆内存分配
计算对象所需空间(12字节头部 + String引用 + int)
-
默认初始化
name=null, age=0
-
显式初始化
执行构造器代码(本例无自定义构造器)
-
建立引用关联
将堆内存地址0x001赋值给栈中的s1变量
-
属性赋值
javas1.name = "阿强"; // 字符串常量池创建"阿强" s1.age = 23; // 直接修改堆内存数据
-
方法调用
s1.study()
通过方法表找到方法区中的字节码执行
四、高频灵魂拷问
Q1:两个对象的study()方法是同一份代码吗?
- 答:是!方法区的字节码被所有对象共享,通过方法表指针访问
Q2:s1和s2的name值如何存储?
- 答:String类型数据存储在字符串常量池,堆中的name字段存储的是指向常量池的引用
Q3:System.out.println(s1)输出的是什么?
- 答:默认调用toString()方法,输出格式:类名@哈希值(如Student@1b6d3586)
五、避坑指南:新手常见误区
-
混淆引用变量与对象
Student s3 = s1;
此时s3与s1指向同一对象,不是复制对象 -
误判方法存储位置
成员方法始终存储在方法区,而非堆内存中
-
忽略字符串特殊性
name="阿强"
实际是在字符串常量池管理,而非直接存储在堆对象中
六、实战训练:试着画出这个场景的内存图
java
Student s3 = new Student();
s3.name = s1.name;
s3.age = s2.age;
s3.study();
提示:注意字符串的引用关系和对象独立性
七、终极总结:3句话掌握对象内存精髓
- 栈管运行:方法调用和局部变量
- 堆管数据:对象实例的具体内容
- 方法区管蓝图:类的结构永存之地
记住 :每个new
都是一次新生,每次.
操作都是一次寻址!