示例【4-4】Person类
public class Person {
String name;
int age;
public void show(){
System.out.println("姓名: " + name + ", 年龄:" + age);
}
}
示例【4-5】TestPerson类
public class TestPerson {
public static void main(String[] args){
// 创建P1对象
Person P1 = new Person();
P1.age = 23;
P1.name = "张三";
P1.show();
// 创建P2对象
Person P2 = new Person();
P2.age = 35;
P2.name = "李四";
P2.show();
}
}
运行TestPerson的内存分析:
1、从TestPerson类开始运行,首先扫描到main方法,将mian()的栈帧进栈,其中存储了相关变量、返回值等。在main()中,也就是形参args,由于没有赋值,所以为空(null)。
2、然后创建变量P1(引用类型,4个字节),此时值为null(Person P1)。与此同时,运行到Person类,这个类的字节码文件就会加载到方法区中临时存储,如show方法。
3、然后调用了 new Person()方法,所以又会开启一个栈帧。
4、new Person()执行时,需要在堆中创建一个P1对象。p1对象创建好后,name默认为null,age为0,show方法不必在每个对象中都保存一边,直接将类信息里的show()地址赋给对象即可。然后将堆中P1对象的地址赋给main()中的P1变量即可。(2、3、4共同组成Person P1 = new Person()语句的内存变化)。运行完这行代码后,new Person()方法调用完成,将其栈帧出栈。
5、P1.age = 23;P1还是在main()方法中,直接根据地址执行,修改P1.age的值。*但是执行P1.name = “张三”;时,并不是直接把“张三”这个字符串直接放在对象所在的堆中,因为字符串在程序编译完之后就已经存储在常量池中了。*所以运行到此时,是将“张三”在常量池中的地址赋给了P1.name.
6、P1.show()是个方法,所以需要重新开启一个方法栈帧。虽然没有显示参数,但是show()是含有隐式参数this的,所以栈帧中有参数this。运行完这行代码后,移除栈帧。
7、然后创建P2对象,创建栈帧,同样的开始也是null。在堆中创建一个新的对象,与P1对象不同,但是结构相同。创建完成后移除栈帧。以相同的方式运行完P2.name。
8、最后执行P2.show(),同样的,也需要创建栈帧。结束后删除栈帧。
9、最后一行代码运行完成后,main()方法也运行完了,删除main()的栈帧。然后整个过程结束,整个堆空间和方法区也删除。