Java对象的实例化过程
一. 首先去JVM 的方法区中区寻找类的Class对象,如果能找到,则按照定义生成对象,找不到则加载类;
二. 加载类定义:类加载器(ClassLoader)寻找该类的 .class文件,找到后对文件进行分析转换为Class对象存入方法区方便以后调用。
其中jdk 的class一般是在JVM启动时用启动类加载器完成加载,用户的Class则是在用到的时候再加载。
Java中ClassLoader的加载采用了双亲委托机制
采用双亲委托机制加载类的时候采用如下的几个步骤:
1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2. 当前ClassLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrap ClassLoader.
3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
Java为什么要采用这样的委托机制?
理解这个问题,我们引入另外一个关于ClassLoader的概念“命名空间”, 它是指要确定某一个类,需要类的全限定名以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。
明白了命名空间以后,我们再来看看委托模型。
采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面说的,我们JDK本生提供的类库,比如Hashmap、LinkedList等等,这些类由bootstrap 类加载器加载了以后,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。
三. 在JVM的堆中给对象开辟一个内存空间
在类的准备阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值。
Person p1 = new Person();
当Person类初始化完成后,系统内存中的存储示意图如下图所示。
可以看出,当Person类初始化完成后,系统将在堆内存中为Person分配一块内存空间,实际上是创建了一个类对象,在这块内存区里包含了保存num类变量的内存,并设置num的默认初始值为0。
四. 对象初始化,顺序:
(1) 父类静态对象,静态代码块
public static int n = 0;// 静态变量
static {// 这是静态初始化块
print();
System.out.println("this is static block");
}
(2)子类静态对象,静态代码块
(3)父类非静态对象,非静态代码块
{ // 实例初始化块
System.out.println(n);
}
(4)父类构造函数
public TestInstanceInit() {// 构造方法
System.out.println("this is TestInstanceInit's constructor~");
}
(5)子类非静态对象,非静态代码块
(6)子类构造函数
系统接着创建了一个Person对象,并把这个Person对象赋给p1变量,Person对象包含了名为name的实例变量,实例变量是在创建实例时分配内存空间并指定初始值的。当创建了第一个person对象后,系统内存中的存储示意图如下图所示。
执行顺序 - 总结如下:
- (静态变量、静态初始化块)>(变量、实例初始化快)>构造器。
- 静态变量和静态初始化块,变量和实例初始化快的先后顺序取决于它们在类中出现的先后顺序。
创建一个对象的过程就是:
检测类是否被加载没有加载的先加载→为新生对象分配内存→将分配到的内存空间都初始化为零值→对对象进行必要的设置→执行方法把对象进行初始化
简而言之
实例化过程分为两个阶段:为对象分配内存空间和初始化对象。
首先使用new运算符为对象分配内存空间,然后再调用构造方法初始化对象。示例代码如下:
String name;
name = new String("Hello World");
代码中String("Hello World")表达式就是调用String的构造方法。初始化完成之后如下图所示: