老规矩,先上图,类生命周期的各个阶段:
类加载主要分为5个过程:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接过程。 (这里涉及到双亲委派,后面我专起一篇文章进行讨论)。
- 加载:通过类加载器读取.class文件中的二进制字节流,并将其转换成Java虚拟机中的Class对象。
- 验证:在加载完成后,Java虚拟机会对类进行验证,以确保它的字节码是正确、安全且符合规范的。
- 准备:为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0,对象类型初始值是null,被final修饰的static字段不会设置,因为final在编译的时候就分配了。
- 解析:解析是Java虚拟机将符号引用替换为直接引用的过程。在Java程序中,调用方法或访问对象时通常使用符号引用,需要在运行时将其解析成直接引用才能执行相应的操作。
- 初始化:当类被加载并初始化后,Java虚拟机会执行其静态初始化器(clinit)中的Java代码。对类中所有静态变量进行赋值,对静态代码块进行执行,它们按照类定义时的顺序依次执行。
下面我们通过一个实例,来更好地理解类的加载过程
先准备一个单例类:
/**
* 定义一个单例
*/
public class Singleton {
//静态变量
private static Singleton singleton = new Singleton();
public static int num1;
public static int num2 = 0;
//静态代码块
static {
System.out.println("num1=" + num1);
System.out.println("num2=" + num2);
}
/**
* 单例的构造函数
*/
private Singleton() {
num1++;
num2++;
}
/**
* 单例
*/
public static Singleton getInstance() {
return singleton;
}
}
通过调用Singleton的static方法触发类加载:
public class LoadClassDemo {
public static void main(String[] args) {
Singleton.getInstance();
}
}
输出结果:
看到输出结果有的小伙伴是不是懵了,同样是执行了++操作,一个结果为1,一个结果为0,下面让我们结合类加载过程来分析分析:
此处调用Singleton的static函数getInstance(),触发类加载,先后经历准备阶段和初始化阶段
1.准备阶段:
(第一步): static Singleton singleton = null;
(第二步): static int num1 = 0;
(第三步): static int num2 = 0;
2.初始化阶段:
(第一步): static Singleton singleton = new Singleton();
在构造函数Singleton()中 num1++;之后 num1=1; num2++;之后 num2=1;
(第二步): static int num1;没有赋值,所以num1保持num1=1不变
(第三步): static int num2 = 0;被赋值为0,所以会把上面的num2=1覆盖,最终值就是num2=0;
(第四步): 执行Singleton的静态代码块,输出num1和num2的值;
希望通过上述实例以及分析,能帮助大家更好地理解类加载过程,后续我跟大家一起讨论双亲委派。