在 Java 中,加载 .class 文件意味着将类的字节码数据从某个来源(如文件系统、网络、内存等)读取,并转换为 Java 虚拟机(JVM)可以使用的 Class 对象,这个过程是 Java 动态性的基石。

主要有以下三种方式:
- 默认的类加载机制 (最常用)
- 自定义类加载器 (最灵活、最强大)
- 使用
URLClassLoader(自定义类加载器的简化版)
默认的类加载机制
这是最常见、最简单的方式,当你编写一个 Java 程序时,JVM 会自动启动一个类加载器(通常是 AppClassLoader)来加载你的类。
工作原理: JVM 使用“双亲委派模型”(Parent Delegation Model)来加载类,加载过程如下:
Bootstrap ClassLoader(启动类加载器):加载 Java 核心库 (JAVA_HOME/jre/lib/rt.jar等),它是 C++ 实现的,是 JVM 的一部分,没有 Java 对象。Extension ClassLoader(扩展类加载器):加载 Java 扩展库 (JAVA_HOME/jre/lib/ext/目录下的 JAR 文件)。Application ClassLoader(应用程序类加载器):加载用户类路径(classpath)下的类,我们通常用Class.forName()或new关键字时,默认就是它来加载。
如何使用: 你不需要做任何特殊操作,JVM 会自动完成。

示例代码:
假设你有一个 MyClass.java 文件,编译后生成了 MyClass.class。
// MyClass.java
public class MyClass {
public void sayHello() {
System.out.println("Hello from MyClass!");
}
}
// Main.java
public class Main {
public static void main(String[] args) {
// 方式1:使用 new 关键字
// JVM 会自动加载 MyClass.class
MyClass obj1 = new MyClass();
obj1.sayHello();
// 方式2:使用 Class.forName()
// 显式地告诉 JVM 加载指定的类
try {
Class<?> clazz = Class.forName("MyClass");
MyClass obj2 = (MyClass) clazz.getDeclaredConstructor().newInstance();
obj2.sayHello();
} catch (Exception e) {
e.printStackTrace();
}
}
}
如何运行:
- 编译:
javac MyClass.java - 运行:
java Main(确保MyClass.class在当前目录下,或者在 classpath 中)
自定义类加载器
当你需要从默认的 classpath 以外的地方加载类时(例如从网络、加密的 JAR 文件、数据库或自定义的文件结构),就需要创建自己的类加载器。
核心步骤:

- 继承
java.lang.ClassLoader类。 - 重写
findClass(String name)方法,这是关键!ClassLoader的loadClass()方法会先遵循双亲委派模型,如果父类加载器无法加载,就会调用我们重写的findClass()方法,我们在这个方法里实现自定义的类查找和加载逻辑。 - 在
findClass方法中,你需要: a. 将类名(如com.example.MyClass)转换为文件路径(如com/example/MyClass.class)。 b. 从你的自定义来源(文件、网络等)读取字节码数据到一个byte[]数组。 c. 调用defineClass(String name, byte[] b, int off, int len)方法,这个方法是ClassLoader提供的,它会将字节数组安全地转换成一个Class对象。
示例代码:
从当前目录下的 custom_classes 文件夹中加载 MyClass.class。
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CustomClassLoader extends ClassLoader {
// 定义类的加载根路径
private final String classRootPath;
public CustomClassLoader(String classRootPath) {
this.classRootPath = classRootPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 1. 将类名转换为文件路径
// "com.example.MyClass" -> "com/example/MyClass.class"
String path = name.replace('.', File.separatorChar) + ".class";
File classFile = new File(classRootPath, path);
if (!classFile.exists()) {
throw new ClassNotFoundException("Class " + name + " not found at " + classFile.getPath());
}
// 2. 从文件中读取字节码
byte[] classBytes;
try (FileInputStream in = new FileInputStream(classFile);
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
classBytes = out.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("Error reading class file: " + classFile.getPath(), e);
}
// 3. 使用 defineClass 将字节码转换为 Class 对象
return defineClass(name, classBytes, 0, classBytes.length);
}
public static void main(String[] args) throws Exception {
// 假设 MyClass.class 放在 ./custom_classes/com/example/ 目录下
// 你需要先创建这个目录结构并复制 MyClass.class 文件进去
CustomClassLoader loader = new CustomClassLoader("./custom_classes");
// 加载类
// 注意:这里的类名是全限定名
Class<?> clazz = loader.loadClass("com.example.MyClass");
// 创建实例并调用方法
Object instance = clazz.getDeclaredConstructor().newInstance();
clazz.getMethod("sayHello").invoke(instance);
}
}
如何运行:
- 创建目录结构:
mkdir -p custom_classes/com/example - 将编译好的
MyClass.class复制到custom_classes/com/example/目录下。 - 编译并运行
CustomClassLoader:javac CustomClassLoader.java java CustomClassLoader
使用 URLClassLoader
URLClassLoader 是 java.net.URLClassLoader 的一个子类,它继承自 ClassLoader,并提供了更方便的方式来从指定的 URL 路径(包括本地文件系统目录、JAR 文件、网站等)加载类,它是对自定义类加载器的一种封装和简化。
核心步骤:
- 创建一个或多个
java.net.URL对象,指向你的类文件或 JAR 文件所在的目录。 - 使用这些
URL对象创建一个URLClassLoader实例。 - 调用
loadClass()方法来加载类。
示例代码:
同样从 custom_classes 目录加载 MyClass.class。
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class UrlClassLoaderExample {
public static void main(String[] args) throws Exception {
// 1. 创建一个指向类文件目录的 URL
File classFileDir = new File("./custom_classes");
URL url = classFileDir.toURI().toURL();
// 2. 创建 URLClassLoader
// 第二个参数是父加载器,通常传 null 表示使用系统默认的 AppClassLoader 作为父加载器
try (URLClassLoader loader = new URLClassLoader(new URL[]{url})) {
// 3. 加载类
Class<?> clazz = loader.loadClass("com.example.MyClass");
// 4. 创建实例并调用方法
Object instance = clazz.getDeclaredConstructor().newInstance();
clazz.getMethod("sayHello").invoke(instance);
}
}
}
如何运行: 步骤与方式二完全相同。
总结与对比
| 特性 | 默认加载机制 | 自定义 ClassLoader | URLClassLoader |
|---|---|---|---|
| 用途 | 加载标准 classpath 下的类。 |
加载非标准来源的类(网络、加密文件、数据库等)。 | 加载指定 URL 路径(目录、JAR)下的类,是自定义加载器的简化版。 |
| 实现 | JVM 自动完成。 | 继承 ClassLoader,重写 findClass()。 |
创建 URL 数组,实例化 URLClassLoader,调用 loadClass()。 |
| 灵活性 | 低。 | 极高,可以完全控制加载逻辑。 | 高,主要用于文件系统和网络 URL。 |
| 典型场景 | 99% 的日常 Java 开发。 | 热部署、插件系统、OSGi、从网络下载代码执行、应用安全沙箱。 | 加载外部 JAR 包、动态加载模块、运行时添加新的类路径。 |
重要提示:破坏双亲委派模型
在某些高级场景下(如 Tomcat 的类加载器),你可能需要破坏双亲委派模型,这可以通过重写 loadClass() 方法来实现,而不是 findClass(),在 loadClass() 方法中,你可以选择不调用 super.loadClass(),而是直接调用 findClass() 来加载类,但这需要谨慎使用,因为它可能带来安全隐患(如加载了有问题的核心库类)。
