在 Java 中,类加载是 Java 虚拟机(JVM)将类的字节码文件从磁盘加载到内存中的过程。ClassLoader
是 Java 中负责类加载的类,它提供了加载类和资源的能力。
Java 类加载过程的基本步骤
Java 中的类加载过程通常遵循以下几个步骤:
-
加载(Loading):
- ClassLoader 会从类路径(Classpath)或其他指定的地方查找字节码文件(.class 文件),并将其读取到内存中。
- 在这个阶段,JVM 会根据全限定类名(包括包名)来查找相应的类文件。
- ClassLoader 会将字节码文件转化为
Class
对象,表示加载的类。
-
验证(Verification):
- 验证阶段是为了保证加载的类符合 JVM 规范,保证字节码的正确性和安全性。
- 验证内容包括:
- 确保字节码文件没有被篡改。
- 确保类中的代码不违反 Java 语言规范。
- 确保代码在执行时不会造成类加载器的安全漏洞。
-
准备(Preparation):
- 这个阶段,JVM 会为类的静态变量分配内存,并设置初始值(默认值)。静态变量在类加载时就会被初始化。
- 这个阶段只为类中的字段分配内存,并不会给它们赋值(除了
final
类型的静态变量,它们会被赋值)。
-
解析(Resolution):
- 在这个阶段,JVM 会将类中的符号引用(如方法调用、字段访问等)转换为直接引用。
- 例如,方法调用时,JVM 会把符号引用解析成真实内存地址。
-
初始化(Initialization):
- 初始化阶段是在类的加载完成后执行的,主要是执行类中的静态初始化块(
static
块)和静态变量的赋值操作。 - 静态代码块在类加载完成后、类的实例化之前执行。
- 如果类有父类,那么父类会先进行初始化。
- 初始化阶段是在类的加载完成后执行的,主要是执行类中的静态初始化块(
ClassLoader 的工作机制
Java 使用 ClassLoader
来负责类的加载。JVM 默认提供了一个类加载器——系统类加载器(System ClassLoader
),但是你也可以自定义 ClassLoader
来加载类。Java 中有三种主要的类加载器:
-
启动类加载器(Bootstrap ClassLoader):
- 启动类加载器是 JVM 的一部分,它负责加载
JRE/lib
目录下的核心类库(如rt.jar
,resources.jar
)。 - 它是所有类加载器的父类加载器。
- 启动类加载器不会直接继承
ClassLoader
类,而是由 JVM 提供实现。
- 启动类加载器是 JVM 的一部分,它负责加载
-
扩展类加载器(Extension ClassLoader):
- 扩展类加载器负责加载
JRE/lib/ext
目录下的类库。 - 它继承自
ClassLoader
,并加载 JDK 扩展库中的类。
- 扩展类加载器负责加载
-
系统类加载器(System ClassLoader):
- 系统类加载器通常由
ClassLoader.getSystemClassLoader()
获取。它负责加载类路径(Classpath)下的类。 - 该加载器负责加载用户自己编写的类文件及第三方库。
- 系统类加载器通常由
ClassLoader 的委托机制
Java 的类加载器采用了一种 委托模型,即当一个类加载器加载类时,它会先委托给父加载器加载。如果父加载器无法加载类,那么子加载器才会尝试加载该类。
- 父类委托机制:
- 类加载器会尝试从其父加载器开始加载类,直到
Bootstrap ClassLoader
。 - 这种机制可以避免类的重复加载。例如,
System ClassLoader
会先请求Extension ClassLoader
加载,如果它加载失败,再由System ClassLoader
加载。 - 这样做的好处是确保核心类库(如
rt.jar
)不会被重复加载,也可以避免用户类与核心类库之间的冲突。
- 类加载器会尝试从其父加载器开始加载类,直到
自定义 ClassLoader
Java 提供了 ClassLoader
类及其子类(如 URLClassLoader
)来实现类加载器的自定义。通过继承 ClassLoader
类,可以实现自己的类加载逻辑。
例如,若你需要从网络中加载类,可以实现一个自定义的 ClassLoader
:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name); // 加载类字节码
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String name) {
// 自定义加载字节码的逻辑(比如从文件、网络等地方加载)
return new byte[0];
}
}
类加载的顺序
- Bootstrap ClassLoader -> Extension ClassLoader -> System ClassLoader(根据类的来源逐级加载)。
类加载的生命周期
- 类的加载是延迟的,即 懒加载。当首次访问某个类时,类才会被加载。
- 类加载的过程是只发生一次的,一旦类被加载,它就会被缓存,并且在整个应用生命周期内保持在内存中,直到 JVM 退出。
类加载器的父子关系
- 类加载器之间存在父子关系,每个类加载器都有一个父类加载器。即使是自定义的类加载器,它也会遵循父子关系。
ClassLoader
类提供了getParent()
方法,可以查看当前类加载器的父类加载器。
总结
- 类加载过程包括加载、验证、准备、解析和初始化阶段。
- Java 的类加载器体系通过 委托机制 确保类的唯一性和安全性。
- Java 提供了三个主要的类加载器:
Bootstrap ClassLoader
、Extension ClassLoader
和System ClassLoader
,以及自定义类加载器的扩展。 - 类加载是懒加载的,且只会加载一次,之后会缓存类的字节码。