今天通过一例子给大家讲解一下Java中对象的创建流程:
public class Main{
public static void main(String[] args){
Person p=new Person("张三",30);
p.speak();
System.out.println(Person.country);
Person.getCountry();
}
}
class Person{
public static final String country="China";
private String name;
private int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public void speak(){
System.out.println(name+":"+age+":"+country);
}
public static void getCountry(){
System.out.println("Person类的国籍是"+country);
}
}
上面的代码我们创建了:
- Person类,实体类,其中包含
静态成员变量 country,成员变量 name 和 age
构造函数 Person() 和 Person(String name,int age)
成员函数 speak()
静态函数 getCountry() - Main类,测试类,其中包含:
创建一个对象并将地址赋给了变量 p
调用构造函数 Person(String name,int age)
调用成员函数 p.speak()
调用静态函数 Person.getCountry()
从编译运行开始到出结果结束,我会通过图示来描述这个对象的创建流程
- 首先编译 javac Main.java
会生成 Main.class 字节码文件 和 Person.class 字节码文件 - 运行前的准备
此时,会将我们编译完成后的字节码文件 Main.class 和 Person.class文件加载进JVM,那么具体会加载进那里?
我们从内存分配角度来分析:
内存主要分为以下五个区域:
1.寄存器
2.本地方法区:我们电脑开机时内存被占%几十,就是系统的运行代码先加载进来,具体就是主要临时存储OS的可执行代码。
3.方法区:主要临时存储我们的应用程序计算机里(如Windows.exe)的可执行代码,Java所写代码就是应用软件,所以.class文件都会被加载进方法区。
4.栈:随着函数的进栈和弹栈,主要运行函数
5.堆:对象堆,主要存储对象数据 - 方法区
方法区分为(static)静态方法区和非静态方法区(简称方法区)
静态方法区 主要存放 静态变量 静态函数
非静态方法区主要存放 构造函数 成员函数 成员变量
里面不仅仅只有函数,也有字段(变量),比如在Person类所属空间里,他有一个static country 静态变量 - 开始运行 java Main
让虚拟机在静态方法区中的Main所属空间里找主函数main()
java Main 就是在Main类所属空间中去找静态主main(),所以 java Person 不行,因为没有主函数。 - 主函数main()进栈
- 然后执行main(),按流程首先看赋值号右边,在堆内存中创建了一个对象
对象根据类中的成员变量的描述,在对象创建后有了具体的成员变量,类中的描述仅仅只是代表有这个变量,只有在对象有了之后,才会有具体的属性,可以总结为 开空间 给地址 对成员变量进行默认初始化。 - 构造函数进栈
1.在这里我们要明确一点,在构造函数刚进栈时,先进行显式初始化,由于的们的Person类中的成员变量没有显式初始化,就执行接下里的针对性初始化,将实参“张三”和“30”的传给形参name 和 age。
2.为了区分哪个对象调用的这个函数?里面有个this字段,哪个对象的调用 就存哪个对象的地址。
3.构造函数里有局部变量 name 和 age,对应的是“张三”和“30”;this.name=name,this.age=age,this指的是对象,所以是将“张三”和“30”在常量池中的地址 给了对象中的成员变量name 和 age。 - 构造函数执行完毕弹栈,将对象的地址赋予左边
- 左边是在主函数中创建Person类型的p变量空间
Person p是主函数当中的Person引用类型局部变量p,所以在栈中main()里开辟空间p,然后将上面创建对象地址给了p - p.speak(),对象调用成员函数
1.表示p指的对象去调用speak函数,speak是成员函数,在非静态方法区去找这段代码。
2.speak()进栈,谁调用的我?同样还存在一个this指的是p的地址,没有局部变量,默认去堆中所指的对象中去找,找到 name 和age 直接打印,System.out.println(name+":"+age+":"+country);
3.所以,成员函数中寻找变量时,先找局部变量,如果没有局部变量,再在堆中找对象,如果还没有,再去静态方法区中找;
局部—堆---静态方法区 就近原则
4.打印完,代码运行完,return 弹栈。 - Person.country,类名调用静态变量
1.在调用静态成员时,可以直接去静态方法区中找
2.Person.xxx Person是因为该区域中有多个类空间 我们得指定一个 - Person.getCountry(),类名调用静态函数
1.System.out.println(“Person类的国籍是”+country);
2.先找局部,没对象不去对象找(没有this),然后直接去静态方法区中找,局部—静态 与堆无关。
3.为什么静态函数中没有this?
因为静态方法区的东西是在对象创建之前就有的!所以去找静态方法区的的静态函数没有this - 图示