什么是 ClassLoader?
ClassLoader 是 Java 虚拟机(JVM)的一部分,它的主要职责是“加载”,当 JVM 需要使用一个类时,ClassLoader 会负责查找类的字节码,并将其转换为 Class 对象,然后将其放入内存中。

Java 的类加载机制采用“双亲委派模型”(Parent Delegation Model),这有助于安全性:
- 当一个 ClassLoader 收到类加载请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器。
- 只有当父类加载器反馈自己无法完成这个加载请求(在它的搜索范围内没有找到所需的类)时,子加载器才会尝试自己去加载。
如何获取 ClassLoader?
主要有以下三种方式,它们获取的 ClassLoader 实例不同,用途也不同。
获取当前类的 ClassLoader
这是最常见的方式,用于加载与当前类在同一位置的资源(如配置文件、属性文件等)。
// 在 MyClass.java 这个类中
public class MyClass {
public void loadResource() {
// 获取加载当前类 (MyClass.class) 的 ClassLoader
ClassLoader classLoader = MyClass.class.getClassLoader();
// 使用这个 ClassLoader 加载资源
// 资源路径相对于 "classpath" (即 /src/main/resources 目录)
InputStream inputStream = classLoader.getResourceAsStream("config.properties");
if (inputStream != null) {
System.out.println("成功找到 config.properties 文件!");
// ... 处理输入流 ...
} else {
System.out.println("未找到 config.properties 文件。");
}
}
}
特点:

- 获取谁? 加载当前类的那个
ClassLoader。 - 通常是谁? 对于在
classpath下的应用代码,通常是AppClassLoader(也叫 System ClassLoader)。 - 适用场景: 加载与当前类同属一个模块或项目的资源文件。
获取当前线程的 Context ClassLoader
这种方式在多线程、特别是涉及框架(如 Spring、Tomcat)和 Java EE 应用时非常重要。
public class MyThreadTask implements Runnable {
@Override
public void run() {
// 获取执行当前代码的线程的 Context ClassLoader
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println("当前线程的 Context ClassLoader: " + contextClassLoader);
// 使用这个 ClassLoader 加载资源
// 这对于在多线程环境中加载特定模块的资源非常关键
InputStream inputStream = contextClassLoader.getResourceAsStream("module-config.xml");
if (inputStream != null) {
System.out.println("成功通过 ContextClassLoader 找到 module-config.xml 文件!");
} else {
System.out.println("未找到 module-config.xml 文件。");
}
}
}
特点:
- 获取谁? 执行当前代码的线程所关联的
ClassLoader。 - 通常是谁? 默认情况下,新线程会继承其创建者线程的
Context ClassLoader,在 Web 服务器(如 Tomcat)中,每个 Web 应用都有自己的ClassLoader,Tomcat 会将请求线程的Context ClassLoader设置为对应 Web 应用的ClassLoader。 - 适用场景:
- 框架开发: Spring 等框架在执行用户代码时,会使用
Context ClassLoader来加载 Bean 定义和配置,确保能加载到用户项目中的类。 - 多线程环境: 当你的线程需要加载的资源不属于默认的
classpath,而是属于某个特定模块或 Web 应用时。 - SPI 机制: Java 的
ServiceLoader使用Context ClassLoader来查找服务实现类,这使得不同模块可以提供自己的服务实现,而无需依赖具体的类加载器。
- 框架开发: Spring 等框架在执行用户代码时,会使用
获取系统 ClassLoader (AppClassLoader)
这是获取应用程序自身类加载器的方式。
public class SystemClassLoaderExample {
public static void main(String[] args) {
// 获取系统 ClassLoader
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统 ClassLoader: " + systemClassLoader);
// 它通常等同于 MyClass.class.getClassLoader()
ClassLoader classLoader = SystemClassLoaderExample.class.getClassLoader();
System.out.println("当前类的 ClassLoader: " + classLoader);
// 判断它们是否是同一个实例
System.out.println("它们是同一个实例吗? " + (systemClassLoader == classLoader));
}
}
特点:

- 获取谁? JVM 启动时由
java.class.path系统属性指定的类路径下的类加载器,即应用程序类加载器。 - 作用: 它负责加载应用程序自身目录(如
jar包或classes目录)中的类。 - 适用场景: 当你需要明确地使用应用程序的类加载器,而不是依赖当前类或线程的上下文时。
ClassLoader 层次结构与 getResource vs getResourceAsStream
理解 ClassLoader 的层次结构对于排查问题至关重要。
层次结构
JVM 中的类加载器通常有三层(或更多,如 Tomcat 会增加 WebAppClassLoader):
-
Bootstrap ClassLoader (启动类加载器)
- 最顶层,由 C++ 实现,是 JVM 自身的一部分。
- 负责加载 Java 核心库 (
JAVA_HOME/jre/lib/rt.jar,resources.jar等)。 - 它没有父加载器,在 Java 代码中获取它返回
null。System.out.println(BootstrapClassLoader.class.getClassLoader()); // 输出 null
-
Extension ClassLoader (扩展类加载器)
- 负责加载 Java 的扩展库 (
JAVA_HOME/jre/lib/ext/*.jar)。 - 它的父加载器是
Bootstrap ClassLoader。
- 负责加载 Java 的扩展库 (
-
Application ClassLoader (应用程序类加载器 / System ClassLoader)
- 负责加载用户类路径(
classpath)上所指定的类库。 - 它的父加载器是
Extension ClassLoader。 - 我们通常通过
ClassLoader.getSystemClassLoader()获取的就是它。
- 负责加载用户类路径(
getResource vs getResourceAsStream
这两个方法都用于从 ClassLoader 中查找资源,但返回类型不同。
-
public URL getResource(String name)- 返回: 一个
java.net.URL对象,代表资源的定位符。 - 如果找不到资源: 返回
null。 - 用途: 当你需要知道资源的完整路径(
file:/...或jar:file:/...)时。
- 返回: 一个
-
public InputStream getResourceAsStream(String name)- 返回: 一个
java.io.InputStream流,可以直接读取资源内容。 - 如果找不到资源: 返回
null。 - 用途: 当你只需要读取资源的内容(如读取配置文件、图片数据等)时,更常用这个方法,因为它更简洁,无需手动关闭
URL连接。
- 返回: 一个
重要提示: getResource 方法会遵循双亲委派模型,它会先让父加载器去查找,如果父加载器找不到,自己才会去查找。
总结与最佳实践
| 获取方式 | 代码示例 | 获取目标 | 适用场景 |
|---|---|---|---|
| 当前类的 ClassLoader | MyClass.class.getClassLoader() |
加载当前类的 ClassLoader |
加载与当前类同属项目的资源文件。最常用。 |
| 当前线程的 Context ClassLoader | Thread.currentThread().getContextClassLoader() |
执行当前代码的线程的 ClassLoader |
多线程环境、框架(Spring)、SPI机制。处理模块化资源的关键。 |
| 系统 ClassLoader | ClassLoader.getSystemClassLoader() |
应用程序类加载器 | 需要明确使用应用主加载器的场景。 |
最佳实践:
- 加载应用资源: 优先使用
MyClass.class.getClassLoader().getResourceAsStream("..."),这是最标准、最不容易出错的方式。 - 在多线程或框架中加载资源: 如果你的代码可能被运行在非默认类加载器环境(在 Tomcat 的一个 Web 应用中),并且需要加载该 Web 应用特有的资源,务必使用
Thread.currentThread().getContextClassLoader(),这是避免ClassNotFoundException或NoClassDefFoundError的关键。 - 避免直接使用 Bootstrap ClassLoader: 普通开发者很少需要直接操作它,当你看到
null作为 ClassLoader 时,通常就代表它。 - 注意资源路径:
getResource的路径是相对于classpath根目录的,**不要以 `/
