类加载器
1. 类加载器的介绍
类加载器(Class Loader)是 JVM 的重要组成部分,负责将 Java 类文件(.class 文件)加载到 JVM 中,并在运行时动态加载类。类加载器通过双亲委派模型工作,确保类的唯一性和安全性。以下是类加载器的详细说明: 
- 双亲委派模型
双亲委派模型是类加载器的工作机制,确保类的唯一性和安全性。其工作流程如下:
- 当一个类加载器收到加载类的请求时,它首先不会自己尝试加载,而是将请求委派给父类加载器。
- 父类加载器会递归地将请求向上委派,直到启动类加载器。
- 如果父类加载器无法加载该类(例如在指定的路径中找不到类),子类加载器才会尝试自己加载。
优点
- 避免重复加载:确保一个类只会被加载一次,防止类的重复加载。
- 安全性:防止核心类库被篡改(例如用户自定义一个 java.lang.String 类,由于双亲委派机制,核心类库中的 String 类会被优先加载)。
2. 类加载器的作用
- 加载类文件:将.class 文件从磁盘或网络加载到 JVM 内存中。
- 生成类对象:为每个加载的类生成一个 java.lang.Class 对象,作为该类在 JVM 中的运行时表示。
- 动态加载:支持在运行时动态加载类,例如通过 Class.forName()方法。
- 隔离类:不同的类加载器可以加载相同名称的类,实现类的隔离(如 Tomcat 中使用不同的类加载器加载不同的 Web 应用)。
3. 类加载器的分类
JVM 中的类加载器主要分为以下四类:
启动类加载器(Bootstrap Class Loader)
- 由 C/C++实现,是 JVM 的一部分。
- 负责加载 JVM 核心类库(如 java.lang.*、java.util.*等),这些类通常位于<JAVA_HOME>/lib 目录下的 rt.jar 等文件中。
- 是类加载器层次结构的顶层,没有父类加载器。
扩展类加载器(Extension Class Loader)
- 由 sun.misc.Launcher$ExtClassLoader 实现。
- 负责加载 Java 的扩展类库(如 javax.*等),这些类通常位于<JAVA_HOME>/lib/ext 目录下。
- 父类加载器是启动类加载器。
应用类加载器(Application Class Loader)
- 由 sun.misc.Launcher$AppClassLoader 实现。
- 负责加载用户类路径(ClassPath)上的类,即开发者编写的类。
- 父类加载器是扩展类加载器。
- 是默认的类加载器,如果没有自定义类加载器,JVM 会使用它来加载类。
自定义类加载器(User-Defined Class Loader)
- 开发者可以通过继承 java.lang.ClassLoader 类,实现自定义的类加载器。
- 常用于实现类的动态加载、热部署、类隔离等场景(如 Tomcat、OSGi 等框架)。
4. 类加载器的加载过程
类加载器加载类的过程可以分为以下三步:
- 加载(Loading)
- 查找类的字节码文件(.class 文件),并将其加载到 JVM 内存中。
- 生成一个 java.lang.Class 对象,作为该类在 JVM 中的运行时表示。
- 链接(Linking)
- 验证(Verification):确保字节码文件的正确性和安全性。
- 准备(Preparation):为类的静态变量分配内存并设置默认初始值。
- 解析(Resolution):将符号引用转换为直接引用。
- 初始化(Initialization)
- 执行类的静态初始化代码(如 static 块)和静态变量的显式赋值。
5. 自定义类加载器的实现
开发者可以通过继承 java.lang.ClassLoader 类,实现自定义的类加载器。以下是一个简单的示例:
代码实现
import java.io.ByteArrayOutputStream; import java.io.InputStream; public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 1. 读取类的字节码文件 byte[] classData = loadClassData(name); if (classData == null) { throw new ClassNotFoundException(); } // 2. 定义类 return defineClass(name, classData, 0, classData.length); } private byte[] loadClassData(String className) { // 从类路径中加载类的字节码 String path = className.replace('.', '/') + ".class"; try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path)) { if (inputStream == null) { return null; } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); int data; while ((data = inputStream.read()) != -1) { outputStream.write(data); } return outputStream.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } }测试类
public class TestClass { public void sayHello() { System.out.println("Hello from TestClass, loaded by: " + this.getClass().getClassLoader()); } }主程序
public class Main { public static void main(String[] args) throws Exception { // 创建自定义类加载器 MyClassLoader myClassLoader = new MyClassLoader(); // 使用自定义类加载器加载 TestClass Class<?> clazz = myClassLoader.loadClass("TestClass"); // 创建实例并调用方法 Object instance = clazz.getDeclaredConstructor().newInstance(); clazz.getMethod("sayHello").invoke(instance); // 打印类加载器信息 System.out.println("TestClass 的类加载器: " + clazz.getClassLoader()); System.out.println("MyClassLoader 的父类加载器: " + myClassLoader.getParent()); } }运行结果
Hello from TestClass, loaded by: MyClassLoader@<hashcode> TestClass 的类加载器: MyClassLoader@<hashcode> MyClassLoader 的父类加载器: sun.misc.Launcher$AppClassLoader@<hashcode>
