类加载过程发生在java程序编译和运行之间,java运行过程可以分为 编译 -> 类加载 -> 执行
类加载由jvm完成,可以分为三步:加载 -> 链接-> 初始化 (这里的加载和类加载不同)
1. 加载
我们知道编译.java文件之后会生成.class文件,这一步就是将编译生成的.class二进制文件加载到内存,放在运行时区域的方法区内供程序使用,然后在堆中创建该类的java.lang.Class对象,这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量。
说明
1) 当程序中第一次主动使用某个类或接口时,jvm从磁盘查找.class文件,加载到方法区内存中,但是此时依然是.class文件,并不能被我们使用,我们还需要一个能被直接使用的对象
2)在堆区为该类创建一个Class对象,这个对象就好像方法区对应类的一个镜子,把方法区存储的类的结构全部反射过来,然后封装起来,成为了一个Class类的对象
java对类的主动使用包括:
1)创建某个类的实例, new A();
2) 访问某个类或接口的静态变量
3)调用类的静态方法
4)反射(如 Class.forName("com.mysql.cj.jdbc.Driver"))
5)初始化一个类的子类(接口除外)
6)Java虚拟机启动时被标明为启动类的类(拥有main方法的类)
需要注意的是,使用类的静态常量时,不会加载类,例如:
class Aa{
public static final int a = 2 ;
static {
System.out.println("Aa 初始化");
}
}
public class TestStatic {
private TestStatic() {
}
public static void main(String[] args) {
System.out.println(Aa.a); //访问静态常量
}
}
输出结果: 2
static代码块没有执行,这是因为常量在编译阶段就已经放入常量池了
2. 链接
链接这一步比较复杂,分为三步:验证 -> 准备 -> 解析
验证:确保.class文件的正确性,即满足java虚拟机规范。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编译规则,这一步就是要过滤掉这部分不合法文件。
准备:为类的静态变量分配内存,将其初始化为默认值,例如int型初始值是0,布尔型初始值是false,引用变量初始值是Null。
解析:把类中的符号引用转化为直接引用。
说明
- 符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
- 直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量内存位置的偏移量。
举例说明,现在调用hello()方法,该方法的地址是0x11112222,hello()就是符号引用,也就是写给程序员看的,让程序员知道这个方法可以这么调用,0x11112222就是直接引用,它负责直接调用方法。
在解析阶段,虚拟机会把所有类名、方法名、字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
3. 初始化
为类的静态变量赋予正确的初始值,上述准备阶段为静态变量赋予的是虚拟机默认的初始值
执行static代码块
如果父类还没有被初始化,则优先初始化父类
参考: