1. 类加载器基础概念
类加载器(ClassLoader)是Java虚拟机(JVM)的重要组成部分,它负责将类的字节码(.class文件)加载到内存中,并在运行时生成对应的Class对象。简单来说,类加载器就是负责"读取"Java类文件的工具。
每个Java类在被加载时,都会记录加载它的ClassLoader实例。通过Class对象的getClassLoader()方法可以获取这个引用。
类加载器的工作流程可以概括为:
- 通过类的全限定名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在内存中生成一个代表该类的Class对象,作为方法区这个类的各种数据的访问入口
2. JVM内置的类加载器
JVM内置了三种重要的类加载器,它们构成了层级关系:
2.1 Bootstrap ClassLoader(启动类加载器)
- 最顶层的类加载器,由C++实现
- 负责加载Java核心类库,如rt.jar、resources.jar等
- 在Java中表示为null(因为它不是Java类)
// 获取String类的类加载器会返回null ClassLoader loader = String.class.getClassLoader(); // null2.2 Extension ClassLoader(扩展类加载器)
- 由Java实现,继承自URLClassLoader
- 负责加载JRE扩展目录(%JAVA_HOME%/lib/ext)中的类
- 父加载器是Bootstrap ClassLoader
2.3 Application ClassLoader(应用程序类加载器)
- 也称为系统类加载器(System ClassLoader)
- 负责加载用户类路径(classpath)上的类库
- 父加载器是Extension ClassLoader
3. 类加载器的双亲委派模型
3.1 工作原理
双亲委派模型(Parents Delegation Model)是类加载器的工作机制:
- 当一个类加载器收到类加载请求时,它首先不会尝试自己加载
- 而是将请求委派给父类加载器完成
- 只有当父加载器无法完成加载时,子加载器才会尝试自己加载
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先检查类是否已加载 Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { // 委派给父加载器 c = parent.loadClass(name, false); } else { // 没有父加载器则委派给Bootstrap ClassLoader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 父加载器无法完成加载 } if (c == null) { // 自己尝试加载 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } }3.2 双亲委派的优势
- 避免重复加载:父加载器已经加载的类,子加载器不会再次加载
- 安全性:防止核心API被篡改(如自定义java.lang.String类)
- 稳定性:保证Java核心库的类型安全
4. 自定义类加载器
4.1 实现步骤
要创建自定义类加载器,通常需要:
- 继承java.lang.ClassLoader类
- 重写findClass()方法(而不是loadClass())
- 在findClass()中实现自己的类加载逻辑
public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 1. 根据类名获取字节数组 byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } // 2. 调用defineClass方法生成Class对象 return defineClass(name, classData, 0, classData.length); } private byte[] getClassData(String className) { // 实现从自定义位置读取.class文件的逻辑 // 例如:文件系统、网络、加密文件等 } }4.2 使用场景
自定义类加载器常用于:
- 热部署:在不重启JVM的情况下重新加载类
- 类加密:加载加密的class文件
- 模块化加载:实现类似OSGi的模块化系统
- 特殊来源加载:从数据库、网络等非标准位置加载类
5. 类加载器常见问题
5.1 如何打破双亲委派模型?
有三种主要方式:
- 重写loadClass()方法(不推荐)
- 使用线程上下文类加载器(Thread Context ClassLoader)
- SPI(Service Provider Interface)机制
// 使用线程上下文类加载器的示例 ClassLoader original = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(myClassLoader); // 执行需要特殊类加载器的代码 } finally { Thread.currentThread().setContextClassLoader(original); }5.2 ClassNotFoundException vs NoClassDefFoundError
- ClassNotFoundException:在类加载阶段,找不到类的定义
- NoClassDefFoundError:在链接阶段,类已加载但无法找到依赖的类
5.3 类加载器与内存泄漏
类加载器与其加载的类之间存在强引用关系,如果不当使用可能导致内存泄漏:
- 长时间存活的对象持有ClassLoader引用
- 动态生成的类没有及时卸载
- 缓存未正确清理
6. 类加载器在主流框架中的应用
6.1 Tomcat的类加载器体系
Tomcat实现了自己的类加载器层级:
- Common:加载Tomcat共享类
- Catalina:加载Tomcat内部类
- Shared:加载Web应用共享类
- WebApp:每个Web应用独立的类加载器
这种设计实现了:
- 应用隔离:不同Web应用的类互不干扰
- 资源共享:公共库只需加载一次
- 热部署:通过重新创建WebAppClassLoader实现
6.2 OSGi的类加载机制
OSGi实现了更精细的类加载控制:
- 每个Bundle有自己的类加载器
- 通过Import-Package和Export-Package控制可见性
- 支持动态安装、更新和卸载Bundle
7. Java 9+模块化对类加载的影响
Java 9引入模块化系统后,类加载器体系有所变化:
- Bootstrap ClassLoader加载java.base等核心模块
- Platform ClassLoader取代Extension ClassLoader
- Application ClassLoader加载应用模块
- 新增Layer概念,支持多版本模块共存
// Java 9+获取平台类加载器 ClassLoader platformClassLoader = ClassLoader.getPlatformClassLoader();模块化系统增强了:
- 更强的封装性
- 更可靠的配置
- 更好的性能
- 更小的内存占用