继上一篇博客【注释和反射】获取class类实例的方法-CSDN博客
目录
三、类加载的过程
例子
三、类加载的过程
在Java虚拟机(JVM)中,类加载是一个将类的字节码文件从文件系统或其他来源加载到JVM的内存中,并将其转换为类或接口的过程。类加载的过程主要可以分为三个步骤:加载(Loading)、链接(Linking)和初始化(Initialization)。
(1)加载(Loading):
加载是类加载过程的第一步,主要由类加载器完成。类加载器负责从文件系统、网络或其他来源读取类的字节码文件,并将其加载到JVM的内存中。加载过程主要完成以下三件事:
- 通过类的全限定名获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
Java提供了三种类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和系统类加载器(System ClassLoader)。这些类加载器之间存在父子关系,但这里的父子关系并非继承关系,而是包含关系。
(2)链接(Linking):
链接过程可以细分为验证(Verification)、准备(Preparation)和解析(Resolution)三个阶段。
- 验证:验证阶段主要是为了确保加载的类文件信息符合JVM规范,不会危害虚拟机自身的安全。
- 验证的内容包括文件格式、元数据、字节码和符号引用等。
- 准备:准备阶段是为类的静态变量(static变量)分配内存,并设置默认的初始值。这些内存都将在方法区中进行分配。
- 需要注意的是,这里所说的初始值通常情况下是数据类型默认的零值,而不是被显式赋值。
- 解析:解析阶段是将常量池内的符号引用替换为直接引用的过程。
- 符号引用以一组符号来描述所引用的目标,而直接引用则是直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
(3)初始化(Initialization):
初始化阶段是类加载过程的最后一步,主要完成类的静态变量的初始化工作。静态变量的初始化是由类构造器<clinit>()
方法完成的。这个方法由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
初始化阶段,只有当类被主动使用时才会触发。主动使用的情况包括:创建类的实例、访问某个类或接口的静态变量、调用类的静态方法、反射(如Class.forName())、初始化一个类的子类(会首先初始化子类的父类)、Java虚拟机启动时被标明为启动类的类(Java Test)、JDK 7开始提供的动态语言支持等。
需要注意的是,类的加载过程必须按照顺序开始,但并非必须按照顺序完成。因为链接阶段中的某些过程(如解析)可能需要在初始化阶段中根据实际运行情况才能确定。此外,JVM规范并没有规定解析阶段一定要在初始化之前完成,只要求了在类和接口被使用前完成解析。
例子
public class Test02 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
class A{
static {
System.out.println("A类的静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A类的无参构造初始化");
}
}
当运行Test02
类的main
方法时,以下是详细步骤:
-
加载
Test02
类:因为Test02
包含main
方法,它是程序的入口点,所以JVM首先加载Test02
类。但是Test02
类本身没有静态变量和静态代码块需要初始化,所以这一步几乎没什么要做的。 -
执行
main
方法:在main
方法中,首先创建A
类的一个实例a
。 -
加载
A
类:为了创建A
的实例,JVM需要加载A
类。在加载类的过程中,JVM会执行以下操作:- 分配内存给
A
类的静态变量。 - 初始化
A
类的静态变量。在这个过程中,首先会执行静态代码块,然后按照声明的顺序初始化静态变量。
- 分配内存给
-
执行静态代码块:在静态代码块中,输出了“A类的静态代码块初始化”,然后将
m
赋值为300。但是,这里有一个关键的陷阱。 -
初始化静态变量
m
:紧接在静态代码块执行之后,静态变量m
被初始化为100。这个赋值操作实际上覆盖了静态代码块中的赋值,因为静态变量的初始化语句(static int m = 100;
)在静态代码块之后执行。 -
创建
A
的实例:现在,A
类已经加载并初始化,可以创建其实例了。调用A
的无参构造函数,输出“A类的无参构造初始化”。 -
输出
m
的值:最后,main
方法中System.out.println(A.m);
输出静态变量m
的当前值,由于m
最终被赋值为100,所以输出结果是100。
综上所述,程序的输出结果为:
A类的静态代码块初始化
A类的无参构造初始化
100