JVM基础篇——类的加载过程
我们先看两个简单实例
实例1:
public class Parent{
public static int parent=0;
static{
System.out.println("Parent init...");
}
}
public class Child extends Parent{
public static int child=Parent.parent;
static{
System.out.println("Child init...");
}
public Child(){
System.out.println("Child init by constructor...");
}
}
public class ClassLoaderTest{
public static void main(String[]args){
System.out.println(Child.parent);
}
}
输出结果:
Parent init...
0
实例2:
public class Child{
public static Child child=Child.getInstance();
public static int num=2;
public static Child getInstance(){
num++;
System.out.println("instance:num="+num);
returnchild;
}
public Child(){
System.out.println("Child init...");
num++;
System.out.println("Constructor:num="+num);
}
}
public class ClassLoaderTest{
public static void main(String[]args){
System.out.println("main:num="+Child.num);
}
}
输出结果:
instance:num=1
main:num=2
这个就有点意思了,为什么instance:num不为2或3,而为1了?这就得从java类的加载机制说起了。容老夫细细道来。
类的加载机制:
类的生命周期
类在JVM中的生命周期分七个阶段:加载、验证、准备、解析、初始化、使用和卸载。其中验证、准备、解析有称为类的连接。加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,而解析可以在初始化之前也可在初始化之后,为了支持java语言的动态绑定(或者叫运行时绑定)。
加载
通过类的全名获取二进制字节流
字节流存入方法区
堆中生成一个Class对象,作为方法区数据结构访问的入口
验证
文件格式验证:是否满足Class文件规范
元数据验证
字节码验证
符号引用验证
准备
类的静态变量会在类准备阶段分配内存
赋予初始值(0,null,false等),而java中显式赋值的操作是在类初始化的时候执行。
类的初始化
类只有在主动使用时,虚拟机才会对类进行初始化,被动使用不会初始化。
主动引用的情况:
New实例化
访问静态变量、静态方法
反射访问
初始化类时,如有父类,父类也会被初始化
Main方法所在类会先被初始化.
被动引用的情况:
调用父类静态变量,子类被称为被动引用,不会初始化。
引用静态变量,定义该常量的类不会初始化
数组定义类
现在,再回过头来分析开始提到的两个实例
实例1:
1:子类调用父类的静态变量,父类会被初始化,子类不会被初始化,也就是说对于静态变量,只有直接定义该变量的类才会被初始化。所以Child.parent时,Child不会初始化,Parent初始化。
2:类的static方法在类初始化的时候会被执行(注意:是初始化时执行,而并非类加载时执行)。所以Child类的static和构造方法都不会被执行。而parent的static方法会被执行。
实例2:
1:类的静态变量会在类准备阶段分配内存,并赋予初始值(0,null等),而java中显式赋值的操作是在类初始化的时候执行。
2:访问child.num时会触发Child的初始化,但是第一行代码child=Child.getinstance()执行时,类尚未完成初始化,所以此时num在内存中的值为0,执行++后为num=1,而当类完成初始化时,num的值又被重置为2,所以main:num=2;
3:如果将Childchild = Child.getInstance();int num = 2;换下位置,可以得到num值都为3,可自行测试。