Java 类加载器(ClassLoader)体系详解
Java 类加载器是 Java 运行时环境的重要组成部分,负责动态加载 Java 类到 JVM 内存中。Java 采用分层的类加载机制,主要有以下几种类加载器:
1. Bootstrap ClassLoader (引导类加载器)
特点:
- 最顶层的类加载器,由 C/C++ 实现,是 JVM 自身的一部分
- 负责加载 Java 核心类库(
JAVA_HOME/jre/lib
下的 rt.jar、resources.jar 等) - 唯一没有父类加载器的加载器
- 在 Java 中表现为
null
(所以调用String.class.getClassLoader()
返回 null)
加载路径:
sun.boot.class.path
系统属性指定的目录- 通常包括:
%JAVA_HOME%/jre/lib
下的核心 jar 包%JAVA_HOME%/jre/classes
目录(已弃用)
2. Extension ClassLoader (扩展类加载器)
特点:
- 由
sun.misc.Launcher$ExtClassLoader
实现 - 父加载器是 Bootstrap ClassLoader
- 负责加载 Java 的扩展库(
JAVA_HOME/jre/lib/ext
目录下的 jar 包) - 开发者可以将通用 jar 包放在此目录下(不推荐,已逐渐被模块化系统取代)
加载路径:
java.ext.dirs
系统属性指定的目录- 默认是
%JAVA_HOME%/jre/lib/ext
目录
3. Application/System ClassLoader (应用/系统类加载器)
特点:
- 由
sun.misc.Launcher$AppClassLoader
实现 - 父加载器是 Extension ClassLoader
- 负责加载应用程序类路径(classpath) 下的类
- 开发者最常打交道的类加载器
- 可通过
ClassLoader.getSystemClassLoader()
获取
加载路径:
java.class.path
系统属性指定的路径- 包括:
- 命令行指定的 classpath
- JAR 文件的类路径
- IDE 项目中的
src/main/resources
等目录
4. 自定义类加载器
开发者可以继承 java.lang.ClassLoader
类实现自己的类加载器,常用于:
- 实现热部署
- 加载非 classpath 下的类
- 实现类隔离(如 Tomcat 为每个 Web 应用使用独立的类加载器)
典型实现方式:
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 自定义类加载逻辑
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
}
类加载器层次关系
Bootstrap ClassLoader (null)
↑
Extension ClassLoader
↑
Application ClassLoader
↑
自定义 ClassLoader
双亲委派模型
Java 类加载采用双亲委派机制(Parent Delegation Model):
- 类加载请求首先交给父加载器处理
- 只有父加载器无法完成加载时,子加载器才会尝试加载
- 这样可以保证核心类库的安全性(防止用户自定义的类替换 Java 核心类)
加载流程:
- 检查类是否已加载
- 未加载则委托父加载器
- 父加载器无法加载时,自己尝试加载
类加载器在模块化系统(Java 9+)中的变化
Java 9 引入模块化系统后:
- Bootstrap、Extension 和 Application 类加载器仍然存在
- Extension ClassLoader 改名为 Platform ClassLoader
- 新增了模块路径(module path)概念
- 类加载机制更复杂,考虑了模块可见性和访问控制
常见问题排查
-
ClassNotFoundException:
- 检查类是否在 classpath 中
- 检查类加载器是否正确
-
NoClassDefFoundError:
- 类加载成功但依赖的类找不到
- 常见于运行时缺少依赖
-
LinkageError:
- 同一个类被不同类加载器加载
- 常见于容器环境(如 Tomcat)