java程序在运行时,需要计算机在内存中为其分配空间。为了提高运行效率,Java对内存区域进行了划分,每一块区域都有其特定的数据处理方式和内存管理方法。其中,Java中内存分配的三大主要空间分别是:栈空间、堆空间和方法区。Java程序在创建对象时,这三大空间的使用情况是如何的呢?
一、数据准备
准备一个实体类People,和测试类TestPeople:
public class People {
//成员变量
private String name = "张一";
private int age = 20;
//构造方法
public People() {
}
//有参构造方法
public People(String name, int age) {
this.name = name;
this.age = age;
}
//Getter and Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class TestPeople {
public static void main(String[] args) {
//使用有参构造器创建对象
People p = new People("张三", 20);
//使用set方法修改成员变量
p.setName("张二");
System.out.println("姓名:" + p.getName() + ",年龄:" + p.getAge());
}
}
执行TestPeople类,输出结果为:
二、执行过程
- 加载含main方法的类的字节码文件到方法区
main方法是程序的唯一入口,运行程序时包含main方法的类的字节码文件,会优先加载到方法区
- main方法进入栈内存
字节码文件加载进入方法区之后,main方法被JVM自动调用,进入栈空间运行
- 执行main方法中的代码
开始,从上往下执行main方法中的代码,每行从右往左执行
- new关键字实例化对象
首先会执行 new People("张三", 20),new关键字表示要实例化People类,但是此时People类的字节码文件并没有加载到方法区。因此,首先要将People类的字节码文件加载到方法区。在将People类的字节码文件加载到方法区的时候,People类中的方法会被自动分配地址。这个地址会在后面用于找到该方法,并执行方法中的代码。假设People类的setName方法地址是0x1111
- 在堆空间中为People对象分配空间,并将这块空间的内存地址值赋值给p。
其中,在堆内存中存放People实例对象的内存区域,还会分为若干区域,分别用于存放实例对象的成员变量值或成员方法的引用地址。实例对象的内存区域中,用于存放成员方法的的区域,存放的并不是成员方法的代码,而是成员方法在方法区中的地址。例如,前面说的setName方法的地址值 0x1111。将来如果对象调用成员方法,可以通过这个地址值找到方法区中的成员方法,然后调用到栈内存中执行。
- 执行默认初始化
对象的默认初始化是默认执行的,会早于其它任何初始化。无论是否给成员变量了设置了默认值,默认初始化都会自动先执行一遍。成员变量的默认初始化值,基本数据类型是0,引用数据类型是null
- 显式初始化
如果在定义People类的时候,为成员变量赋了初始化值,则在默认初始化后,还会进行第二次初始化(显示初始化)。如果在定义类的时候,没有对成员变量赋初值,则省略显式初始化。在定义People类的时候,对name和age都赋了初值,因此,这里还要进行显式初始化:
ps.在显式初始化时,name属性的初始化值是"张一"。其中"张一"这个字符串的数据值,并不是直接保存在对象的内存区域中。对象中保存的实际是,字符串在常量池中的地址。
- 构造器初始化
在经过隐式初始化和显式初始化,接下来需要进行构造器初始化。如果使用的是无参构造器,则不再需要进行构造器初始化,但是我们使用了有参构造器,因此就需要进行构造器初始化,即把对象的属性值修改为有参构造器传入的形参值。
至此,People类实例化对象的初始化就算完成了。new关键字执行的操作结束,并把对象在堆内存中的地址值返回给 p 引用。
- 继续执行main方法中的代码
执行完new People后,接着执行的代码是p.setName。通过"对象名."的方式调用对象成员的方式,本质就是通过p的引用地址,找到对象在堆内存中的位置,再接着在堆内存中获取成员变量的值,或成员方法的地址。然后根据成员方法的地址接着去找到成员方法并执行。在本案例中p.setName的执行过程就是:
- 通过p指向的0x2222找到对象在堆内存中位置
- 在0x2222中找到,setName的内存地址 0x1111
- 调用0x1111位置的setName方法进栈内存中执行
- 继续执行main方法
setName方法中的代码执行完成后,setName方法会出栈,并继续执行main方法中的代码:
至此,Java创建对象并执行对象成员方法的内存调用流程就演示完了。
参考
评论 (0)