在 Java 中,类加载是将类从文件系统或其他地方加载到内存中的过程。Java 采用了延迟加载的方式,只有在需要使用某个类时,JVM(Java 虚拟机)才会加载它。
类加载的过程
类整个生命周期可以概括上图所示过程:加载、验证、准备、解析、初始化、使用和卸载。其中,验证、准备和解析这三个阶段可以统称为连接。
- 加载
类加载器首先会通过类的全限定名(例如 com.example.MyClass
)查找该类对应的 .class
文件,将文件中的二进制数据读入到内存中,其静态存储结构转化为方法区的运行时数据结构,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
-
连接
- 验证 验证的主要作用就是确保被加载的类的正确性,止恶意代码或不合法的类文件进入 JVM。会进行文件格式、元数据、字节码、符号引用验证。
- 准备
该阶段为类的静态变量分配内存并设置初始值。初始化的仅仅是类变量(static),并给其对应类型的默认零值(如
0
、0L
、null
、false
等)。但如果该变量同时还被final修饰,那其在该阶段的初始值为实际的值,而不是零值。 - 解析 将类中的符号引用(比如方法和变量的名字)转化成可以直接访问的内存地址。
-
初始化
在类的初始化阶段,JVM 会执行类的静态初始化块(static {}
)以及静态字段的初始化。如果类包含静态代码块或静态变量,它们将在初始化阶段被执行和赋值。
在上述的五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。
类加载器
类加载是由 类加载器(ClassLoader) 实现的。
Java 三种常见的类加载器:
- 启动类加载器(Bootstrap ClassLoader)
启动类加载器是 JVM 自带的,它负责加载 Java 核心类库(比如 java.lang.String
)。启动类加载器通常是用 C 或 C++ 编写的。
- 扩展类加载器(Extension ClassLoader)
扩展类加载器负责加载 Java 扩展库,通常是 jre/lib/ext/
目录中的类库。它的父类加载器是启动类加载器。
- 应用类加载器(AppClassLoader)
应用类加载器是最常用的加载器,它负责加载你应用程序中的类(比如 JAR 包中的类)。它的父类加载器是扩展类加载器。
双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
大致执行过程:
-
当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
-
当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
-
如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
-
若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。