类加载器是 Java 虚拟机(JVM)的一个核心组成部分,负责在程序运行时动态地将 .class
文件(字节码)加载到 JVM 中,并将其转换为 Java 的 java.lang.Class
对象。
类加载器使得 Java 能够在运行时动态加载类,支持了诸如热部署、插件化、模块化等高级特性。
类加载器的职责
- 加载(Loading):通过类的全限定名(Fully Qualified Name)获取其对应的字节码,并将其读入内存,创建一个
Class
对象。 - 链接(Linking):
- 验证(Verification):确保加载的字节码是合法且安全的,符合 JVM 规范。
- 准备(Preparation):为类的静态变量分配内存,并设置默认初始值(如
0
,false
,null
)。 - 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用(可选在初始化时进行)。
- 初始化(Initialization):执行类的静态初始化块和静态变量的赋值操作,完成类的最终准备。
Java 中的类加载器层次结构
Java 的类加载器采用 双亲委派模型(Parent Delegation Model),形成一个层次结构:
- 启动类加载器(Bootstrap ClassLoader):
- 由 C/C++ 实现,是 JVM 的一部分,不是 Java 对象。
- 负责加载 Java 核心类库,位于
JAVA_HOME/jre/lib
目录下(如rt.jar
),例如java.lang.*
、java.util.*
等。 - 无法被 Java 程序直接引用。
- 扩展类加载器(Extension ClassLoader / Platform ClassLoader):
- 由 Java 实现,通常是
sun.misc.Launcher$ExtClassLoader
(旧版本)或jdk.internal.loader.ClassLoaders$PlatformClassLoader
(JDK 9+ 模块化后)。 - 负责加载 Java 的扩展库,位于
JAVA_HOME/jre/lib/ext
目录下,或由系统属性java.ext.dirs
指定的路径。 - 它的父类加载器是 Bootstrap ClassLoader。
- 由 Java 实现,通常是
- 应用程序类加载器(Application ClassLoader / System ClassLoader):
- 由 Java 实现,通常是
sun.misc.Launcher$AppClassLoader
。 - 负责加载用户类路径(Classpath)上指定的类,即我们自己编写的代码和第三方 jar 包。
- 它的父类加载器是 Extension/Platform ClassLoader。
- 这是默认的类加载器,
ClassLoader.getSystemClassLoader()
返回的就是它。
- 由 Java 实现,通常是
- 自定义类加载器(Custom ClassLoader):
- 开发者可以通过继承
java.lang.ClassLoader
抽象类来创建自己的类加载器。 - 用于实现特殊需求,如热部署、隔离加载、加密类加载、模块化(OSGi)、远程类加载等。
- 开发者可以通过继承
双亲委派模型的工作机制
当一个类加载器收到类加载请求时,它不会立即自己去加载,而是将这个请求委派给其父类加载器去完成。只有当父类加载器无法完成加载(返回 ClassNotFoundException
)时,子类加载器才会尝试自己去加载。
工作流程:
- 应用程序类加载器收到请求。
- 委托给扩展类加载器。
- 扩展类加载器再委托给启动类加载器。
- 启动类加载器尝试加载,如果找不到,则返回失败。
- 扩展类加载器尝试加载,如果也找不到,返回失败。
- 最终由应用程序类加载器尝试加载。
双亲委派模型的优点
- 避免重复加载:确保一个类只会被加载一次。
- 安全性:防止核心 API 被恶意篡改。例如,即使你写了一个
java.lang.String
类,由于双亲委派,Bootstrap ClassLoader 会优先加载 JDK 中的String
,保证了核心类库的稳定性和安全性。
破坏双亲委派的场景
虽然双亲委派是推荐模型,但在某些特殊场景下会被“破坏”:
- SPI(Service Provider Interface):如 JDBC、JNDI 等。父类加载器(Bootstrap)需要加载由用户实现的接口实现类(在应用 ClassPath 下),但父类加载器无法访问应用类路径。此时通过
Thread.currentThread().getContextClassLoader()
获取应用类加载器来完成加载。 - OSGi 模块化框架:为了实现模块间的类隔离和动态加载,OSGi 实现了自己复杂的类加载机制,打破了传统的双亲委派。
- 热部署/热更新:为了实现模块的独立加载和卸载,需要自定义类加载器并绕过双亲委派。
总结
类加载器是 Java 实现动态性、灵活性和安全性的基石。
理解类加载器的层次结构、双亲委派模型及其工作原理,对于深入掌握 Java 运行机制、排查类加载问题(如 ClassNotFoundException
、NoClassDefFoundError
、ClassCastException
等)以及开发高级框架(如 Spring、OSGi、应用服务器)都至关重要。
THE END