JVM(17)之 准备-解析-初始化
在类加载机制的五个阶段中,我们已经讲完了第一个阶段。剩下的四个阶段由于涉及到比较多的类文件相关的知识,现在讲了会看得很吃力,所以我们暂时不会一一的去细讲,只说一下大概的用处,让大家有个概念性的认识。
装载之后的阶段就是校验阶段了,该阶段的目的就是确保上一阶段读进来的二进制字节流中包含的信息符合虚拟机的规范,并且不会危害虚拟机自身。校验主要分为四个方向:文件格式校验、元数据校验、字节码校验和符号引用校验。
校验过后就是准备阶段了。该阶段就是为类变量分配内存以及设置初始值。注意:这里的分配内存和设置初始值针对仅仅只是类变量,如:public static int val = 123;这种被static修饰的变量,但是这个设置初始值并不是将例子中的123设置给val变量,而是将0设置给该变量。因为这里指的是初始值,也就是默认的值。如boolean型默认的就是false;float型默认的就是0.0f;引用类型就默认为null。
然后就是解析阶段,该阶段就是将符号引用替换为直接引用的过程。这两个名词我们会在后面讲类文件格式的时候仔细讲,大家先记着有这么一个阶段就好了。
最后就是初始化的阶段了,到了这个阶段才会开始执行我们自己写在类中的代码。他执行的是类变量和静态块。如下面代码:
还记得刚刚讲的准备阶段吗?在准备阶段是为类变量设置初始值和分配内存(方法区分配),而在该阶段则是为该变量赋上我们自己设置的值。到此,小伙伴知道为什么类方法(静态方法)不能调用普通方法或者普通变量了吗?因为类变量以及静态块各相关方法都在准备阶段分配了内存,在初始化阶段就赋予了值,而此时其他普通的变量并没有做到这几步,他们都是在生成实例变量的时候才会进行内存的分配(堆中分配),因此如果静态方法调用了一个普通变量,而此时还没有创建该普通变量的对象,这就会导致系统错误。所以为了避免这种情况,就不允许那样调用了。
这也是为什么main方法是静态方法的原因之一,因为不用创建对象就可调用了。创建对象是要消耗内存的!!
在类加载的前面四个阶段中,虚拟机都没有硬性的规定在什么情况下才能进行,而初始化阶段则有且只有5种情况下才能进行,基于易理解的角度来考虑,我们暂时只说3种:
- 1:当虚拟机启动的时候,虚拟机会初始化包含main方法的那个类。
- 2:当初始化一个子类的时候,发现其父类还没有初始化,就会先初始化其父类。
- 3:当我们使用反射对类进行调用的时候,如果该类没有进行初始化,就会先初始化。在我们用JDBC的时候,我们经常会看到这样一行代码:
Class.forName("com.mysql.jdbc.Driver");
这就是反射调用,加载该类并且初始化。