JVM通过类加载器将字节码加载并完成验证、准备、解析、初始化;内置启动、扩展、应用程序三类加载器构成双亲委派模型,支持自定义加载器实现热部署、插件隔离、安全沙箱等场景。

Java 虚拟机(JVM)通过类加载器(ClassLoader)将字节码文件加载到内存中,并完成验证、准备、解析、初始化等过程。JVM 提供了三类内置类加载器,同时支持开发者自定义类加载器以满足特殊需求,比如热部署、隔离加载、加密类文件等。
三大内置类加载器及其职责
JVM 启动时会创建以下三个层级的类加载器,它们构成双亲委派模型的基础:
-
启动类加载器(Bootstrap ClassLoader):由 C++ 实现,负责加载 $JAVA_HOME/jre/lib 下的核心类库(如
rt.jar、resources.jar),无法被 Java 程序直接引用(getClassLoader()返回null)。 -
扩展类加载器(Extension ClassLoader):由
sun.misc.Launcher$ExtClassLoader实现,加载 $JAVA_HOME/jre/lib/ext 目录或java.ext.dirs指定路径下的 JAR 包。 -
应用程序类加载器(Application ClassLoader):也称系统类加载器,由
sun.misc.Launcher$AppClassLoader实现,负责加载classpath指定路径下的类(包括主函数所在类),是默认的用户类加载器。
双亲委派机制的工作原理
当一个类加载器收到类加载请求时,它不会立即尝试加载,而是先委托父类加载器去完成;只有当父类加载器无法加载(即在对应路径找不到该类)时,子加载器才尝试自己加载。这种机制保证了:
- 核心类(如
java.lang.Object)始终由启动类加载器加载,避免被篡改或重复定义; - 不同类加载器加载的同名类被视为不同类型,可实现类隔离(如 Web 容器中各应用独立加载自己的
spring-core)。
如何编写一个简单的自定义类加载器
自定义类加载器通常继承 java.lang.ClassLoader,重写 findClass(String name) 方法(不建议直接重写 loadClass,以免破坏双亲委派):
- 将类名转换为路径(如
com.example.MyClass→/com/example/MyClass.class); - 读取对应的字节码数组(可从文件、网络、数据库或解密后获取);
- 调用
defineClass(name, bytes, offset, len)将字节码转为Class对象。
示例关键代码片段:
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
if (data == null) {
throw new ClassNotFoundException();
}
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name) {
String path = classPath + File.separatorChar
+ name.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
int b;
while ((b = is.read()) != -1) baos.write(b);
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}
}
自定义类加载器的典型应用场景
- 热部署与热替换:在不重启 JVM 的前提下重新加载更新后的类(如 Spring Boot DevTools、JRebel);
- 插件化架构:不同插件使用独立类加载器加载,实现类隔离与卸载能力(如 OSGi、IDEA 插件平台);
- 安全沙箱:限制第三方代码访问敏感 API 或仅允许加载签名验证通过的类;
- 动态脚本支持:运行时编译并加载 Groovy、Janino 等脚本生成的字节码。










