🔄 Java的类加载器(ClassLoader)详解
类加载器(ClassLoader) 是Java虚拟机(JVM)中负责加载 .class
文件的组件。它通过将类文件读入内存,并将其转化为 Class
对象,从而使程序能够使用这些类。类加载器采用 双亲委派模型(Parent Delegation Model),保障了Java核心库的安全性与一致性。
下面详细介绍类加载器的类型、工作机制以及双亲委派模型。
1. 🌟 类加载器的类型
Java中主要有以下几种类加载器:
1.1 启动类加载器(Bootstrap ClassLoader)
- 作用: 加载核心Java类库(如
rt.jar
,包含java.lang.*
、java.util.*
等)。 - 实现: 用C/C++语言编写,属于JVM的一部分。
- 默认路径:
JAVA_HOME/lib
目录或通过-Xbootclasspath
指定。 - 特点: 无法直接获取,
ClassLoader.getParent()
返回为null
。
1.2 扩展类加载器(Extension ClassLoader)
- 作用: 加载扩展库中的类,如
JAVA_HOME/lib/ext
目录下的.jar
文件。 - 实现: 由
sun.misc.Launcher$ExtClassLoader
实现。 - 父加载器: 启动类加载器。
- 特点: 可以通过
ClassLoader.getParent()
获取。
1.3 应用程序类加载器(AppClassLoader / System ClassLoader)
- 作用: 加载应用程序路径(
classpath
)中的类,通常是我们编写的代码。 - 实现: 由
sun.misc.Launcher$AppClassLoader
实现。 - 父加载器: 扩展类加载器。
- 特点: 默认的上下文类加载器 (
Thread.currentThread().getContextClassLoader()
)。
1.4 自定义类加载器(Custom ClassLoader)
- 作用: 用户可以继承
java.lang.ClassLoader
编写自己的类加载器,实现自定义的类加载逻辑(如从网络、数据库加载类)。 - 使用场景: 插件机制、隔离加载、热部署等。
- 常用方法:
findClass(String name)
:查找类的二进制字节流。defineClass(String name, byte[] b, int off, int len)
:将字节流转化为Class
对象。
2. 🌀 双亲委派模型(Parent Delegation Model)
核心思想: 当一个类加载器加载类时,先委托父加载器尝试加载,只有当父加载器无法加载时,才尝试自己加载。
加载过程:
- 检查缓存: 先检查当前类是否已加载(通过
findLoadedClass
方法)。 - 委托父类: 如果未加载,先委托父加载器
loadClass
。 - 自行加载: 如果父加载器无法加载,调用
findClass
方法加载。 - 缓存结果: 加载成功后缓存
Class
对象。
优点:
- 安全性: 核心API不会被篡改(如
java.lang.String
)。 - 避免重复加载: 确保同一类只被加载一次。
示例代码:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 自定义类加载逻辑,比如从网络或加密文件中加载
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
}
3. 🛠 类加载器的常用方法
loadClass(String name)
: 加载类,遵循双亲委派模型。findClass(String name)
: 查找类,需重写此方法以实现自定义加载。getParent()
: 获取父加载器。defineClass()
: 将字节数组转化为Class
对象。
4. 🌳 类加载器的层次结构
Bootstrap ClassLoader (启动类加载器)
↑
Extension ClassLoader (扩展类加载器)
↑
AppClassLoader (应用程序类加载器)
↑
CustomClassLoader (自定义类加载器)
特点:
- 自定义类加载器可以选择是否遵循双亲委派模型。
- 每个类加载器只能加载自己路径或资源范围内的类。
5. ⚠️ 破坏双亲委派模型的场景
场景:
- Tomcat、OSGi、JSP容器 为了支持热部署、模块化,会自定义类加载器。
- SPI(Service Provider Interface): 通过线程上下文类加载器加载第三方库。
示例:线程上下文类加载器
Thread.currentThread().setContextClassLoader(new CustomClassLoader());
Class<?> clazz = Class.forName("com.example.MyClass", true,
Thread.currentThread().getContextClassLoader());
6. 📌 总结
- 启动类加载器:加载核心库,无法获取。
- 扩展类加载器:加载扩展库,基于
lib/ext
目录。 - 应用程序类加载器:加载
classpath
下的类。 - 自定义类加载器:灵活性强,可用于模块化、热部署等场景。
- 双亲委派模型:保障安全性和一致性,但可被自定义类加载器破坏。