在开发中,有时会遇到这样的情况:我明明给一个变量赋值了,为什么在使用该变量时却是没有值的,这个和JVM的内存加载顺序有关,当你使用该变量时,这个变量还没初始化完成。
首先我们来看一段代码:
public class ObjectLoadMemoryTest extends FuTest{
private int INT =100;
private final int FINAL_INT =100;
private final Integer FINAL_INTEGER = 100;
private static Integer STATIC_INTEGER = 100;
private String STR1 = "abc";
private final String FINAL_STR1="abc";
private final String FINAL_STR2 = new String("abc");
private final List<String> FINAL_LIST = new ArrayList<String>();
public ObjectLoadMemoryTest() {
System.out.println("ObjectLoadMemoryTest开始执行构造。。。。。。");
doDisplay();
}
@Override
public void doDisplay() {
System.out.println(INT);
System.out.println(FINAL_INT);
System.out.println(FINAL_INTEGER);
System.out.println(STATIC_INTEGER);
System.out.println(STR1);
System.out.println(FINAL_STR1);
System.out.println(FINAL_STR2);
System.out.println(FINAL_LIST);
}
public static void main(String[] args) {
new ObjectLoadMemoryTest();
}
}
abstract class FuTest{
public FuTest(){
System.out.println("FuTest构造开始执行。。。。。");
doDisplay();
}
public abstract void doDisplay();
}
这段代码的逻辑很简单,就是在初始化子类对象的时候,调用这个类中的方法doDisplay,这个方法会打印该类中的所有的属性,我们可以通过观察打印结果来分析一下jvm在创建对象的时候,内存是如何加载的。
运行结果如下:
FuTest构造开始执行。。。。。
0
100
null
100
null
abc
null
null
ObjectLoadMemoryTest开始执行构造。。。。。。
100
100
100
100
abc
abc
abc
[]
我们发现在调用子类的构造函数的时候,会先调用父类的构造,在父类中我们同样会打印所有的属性,发现和子类中打印的结果是不一样的。
通过打印结果,我们可以分析到以下几点:
- 基本数据类型的默认初始化是在构造函数之前,但是其显示初始化却是在调用父类构造函数之后执行的
- 使用final修饰的基本数据类型及String类型,在调用父类构造函数之前就已经显示初始化完成了
- 即使使用了final修饰,属性如果是一个对象,那么它也会在调用父类构造函数之后执行
- 使用static修饰的属性,都是在调用父类构造之前就显示初始化了
由此,我们可以推断出如下结果:
类的加载过程:
1、启动JVM,加载程序中需要使用的class文件。
2、在加载class文件的时候,所有的静态内容(静态成员变量,静态成员函数,静态代码块)都要加载到方法区的静态区中。
3、当类中的所有静态加载完成之后,开始给类中的所有静态成员变量默认初始化。
4、类中的所有静态成员变量默认初始化完成之后,开始给这些静态成员变量显示赋值。
5、所有静态成员变量显示赋值结束之后,开始运行类中的静态代码块。
6、当所有的静态代码块执行完成,代表当前这个class文件才彻底被加载结束。
对象的创建过程:
1、使用new关键字创建对象,在堆给对象分配内存空间。
2、给对象所属类中的所有非静态成员变量分配空间并进行默认的初始化。
3、执行和new对象时传递参数一致的构造函数。
4、执行构造函数的的过程中有隐式的三步:
4.1、执行super() 语句,找父类的空参数构造函数
4.2、给非静态成员变量进行显示赋值。
4.3、运行构造代码块
4.4、构造函数中的自己写的代码执行。
5、构造函数执行完成,对象创建结束。