09-Java语言核心-JVM原理-类加载机制详解

4 阅读20分钟

类加载机制详解

一、知识概述

Java虚拟机的类加载机制是Java语言的核心特性之一。JVM将描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。理解类加载机制对于理解Java程序运行原理、框架开发和问题排查都至关重要。

类加载过程概览

┌─────────────────────────────────────────────────────────────────────┐
│                        类加载完整流程                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐        │
│  │   加载   │──►│   连接   │──►│   初始化  │──►│   使用   │        │
│  │  Loading │   │ Linking  │   │Initialize│   │  Using   │        │
│  └──────────┘   └──────────┘   └──────────┘   └──────────┘        │
│                      │                                              │
│                      ▼                                              │
│               ┌─────────────────────┐                              │
│               │      连接细分       │                              │
│               ├─────────────────────┤                              │
│               │  ┌─────────┐        │                              │
│               │  │  验证   │        │                              │
│               │  │ Verify  │        │                              │
│               │  └────┬────┘        │                              │
│               │       ▼             │                              │
│               │  ┌─────────┐        │                              │
│               │  │  准备   │        │                              │
│               │  │ Prepare │        │                              │
│               │  └────┬────┘        │                              │
│               │       ▼             │                              │
│               │  ┌─────────┐        │                              │
│               │  │  解析   │        │                              │
│               │  │ Resolve │        │                              │
│               │  └─────────┘        │                              │
│               └─────────────────────┘                              │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

类加载的时机

阶段触发条件
加载遇到new/getstatic/putstatic/invokestatic四条指令
加载使用java.lang.reflect包方法反射调用
加载初始化类的子类时,父类需要先初始化
加载虚拟机启动时的主类(main方法所在类)
加载MethodHandle/VarHandle相关的类

二、知识点详细讲解

2.1 类加载过程

2.1.1 加载(Loading)
/**
 * 类加载阶段详解
 * 
 * 加载阶段完成的三个任务:
 * 1. 通过类的全限定名获取定义此类的二进制字节流
 * 2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构
 * 3. 在内存中生成一个代表这个类的java.lang.Class对象
 */
public class LoadingDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 类加载阶段 ===\n");
        
        /*
        加载源:
        
        1. 本地文件系统
           - CLASSPATH下的.class文件
           - JAR包中的.class文件
        
        2. 网络
           - Applet从网络加载
           - 动态代理生成的类
        
        3. ZIP压缩包
           - JAR、WAR文件
        
        4. 运行时计算生成
           - 动态代理:$Proxy类
           - ASM、CGLib生成的类
        
        5. 数据库
           - 从数据库读取字节码
        
        6. 其他文件
           - JSP编译成的class
        */
        
        // 示例:观察类加载过程
        loadClassDemo();
    }
    
    /**
     * 观察类加载过程
     */
    private static void loadClassDemo() {
        System.out.println("【类加载示例】\n");
        
        // 获取类加载器
        ClassLoader loader = LoadingDemo.class.getClassLoader();
        System.out.println("类加载器: " + loader);
        
        // 加载类(使用Class.forName触发加载)
        try {
            // forName会执行加载、连接、初始化
            Class<?> clazz = Class.forName("java.util.ArrayList");
            System.out.println("加载成功: " + clazz.getName());
            
            // 获取类信息
            System.out.println("类名: " + clazz.getSimpleName());
            System.out.println("包名: " + clazz.getPackage().getName());
            System.out.println("修饰符: " + java.lang.reflect.Modifier.toString(clazz.getModifiers()));
            
            // Class对象在堆中,类的元数据在方法区
            System.out.println("\nClass对象存储在: Java堆");
            System.out.println("类元数据存储在: 方法区(元空间)");
            
        } catch (ClassNotFoundException e) {
            System.out.println("类未找到: " + e.getMessage());
        }
        
        System.out.println();
        
        // 使用ClassLoader.loadClass只加载不初始化
        System.out.println("ClassLoader.loadClass区别:");
        System.out.println("  Class.forName(): 加载+连接+初始化");
        System.out.println("  ClassLoader.loadClass(): 仅加载");
    }
}

/**
 * 验证类加载顺序
 * 使用-XX:+TraceClassLoading参数查看
 */
class ClassLoadingOrder {
    static {
        System.out.println("ClassLoadingOrder 类初始化");
    }
    
    public static void main(String[] args) {
        System.out.println("main方法开始");
        
        // 使用new触发类加载
        new Child();
        
        System.out.println("main方法结束");
    }
}

class Parent {
    static {
        System.out.println("Parent 静态代码块");
    }
    
    {
        System.out.println("Parent 实例代码块");
    }
    
    public Parent() {
        System.out.println("Parent 构造方法");
    }
}

class Child extends Parent {
    static {
        System.out.println("Child 静态代码块");
    }
    
    {
        System.out.println("Child 实例代码块");
    }
    
    public Child() {
        System.out.println("Child 构造方法");
    }
}

/*
执行结果:
  ClassLoadingOrder 类初始化
  main方法开始
  Parent 静态代码块
  Child 静态代码块
  Parent 实例代码块
  Parent 构造方法
  Child 实例代码块
  Child 构造方法
  main方法结束
*/
2.1.2 连接(Linking)
/**
 * 连接阶段详解
 * 包含:验证、准备、解析三个子阶段
 */
public class LinkingDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 连接阶段 ===\n");
        
        verifyDemo();
        prepareDemo();
        resolveDemo();
    }
    
    /**
     * 验证阶段(Verify)
     */
    private static void verifyDemo() {
        System.out.println("【1. 验证阶段】\n");
        
        /*
        验证内容:
        
        1. 文件格式验证
           - 是否以魔数0xCAFEBABE开头
           - 主次版本号是否在当前虚拟机支持范围
           - 常量池的常量是否有不支持的类型
           - 索引值是否指向正确的位置
        
        2. 元数据验证
           - 类是否有父类(除了Object)
           - 父类是否继承了不允许继承的类(final类)
           - 是否实现了接口的所有方法
           - 字段、方法是否与父类冲突
        
        3. 字节码验证
           - 操作数栈的数据类型与指令代码序列匹配
           - 跳转指令不会跳转到方法体外
           - 类型转换是否有效
        
        4. 符号引用验证
           - 符号引用的全限定名是否能找到对应的类
           - 符号引用中的类、字段、方法是否可访问
        */
        
        System.out.println("验证目的: 确保Class文件字节流符合要求");
        System.out.println("验证内容:");
        System.out.println("  1. 文件格式验证 - Class文件格式正确");
        System.out.println("  2. 元数据验证 - 语义分析");
        System.out.println("  3. 字节码验证 - 数据流和控制流分析");
        System.out.println("  4. 符号引用验证 - 引用的类、方法存在且可访问");
        System.out.println();
        
        // 魔数示例
        System.out.println("Class文件开头:");
        System.out.println("  CA FE BA BE 00 00 00 34 ...");
        System.out.println("  (魔数0xCAFEBABE,版本52.0 = JDK 8)");
        System.out.println();
        
        // 关闭验证(不推荐)
        System.out.println("关闭验证参数: -Xverify:none (不推荐)");
        System.out.println();
    }
    
    /**
     * 准备阶段(Prepare)
     */
    private static void prepareDemo() {
        System.out.println("【2. 准备阶段】\n");
        
        /*
        准备阶段任务:
        为类的静态变量分配内存,并设置默认初始值
        
        注意:
        - 此时只分配内存,不执行<clinit>方法
        - 基本类型设置为0值
        - 引用类型设置为null
        - final类型在准备阶段就会被赋值
        */
        
        System.out.println("准备阶段为静态变量分配内存并设置初始值:\n");
        
        System.out.println("示例代码:");
        System.out.println("  public static int value = 123;");
        System.out.println("  public static final int CONST = 456;");
        System.out.println();
        
        System.out.println("准备阶段后:");
        System.out.println("  value = 0  (初始值,非123)");
        System.out.println("  CONST = 456 (final变量,直接赋值)");
        System.out.println();
        
        System.out.println("初始化阶段后:");
        System.out.println("  value = 123 (执行<clinit>方法后)");
        System.out.println();
        
        System.out.println("默认初始值表:");
        System.out.println("  int     -> 0");
        System.out.println("  long    -> 0L");
        System.out.println("  float   -> 0.0f");
        System.out.println("  double  -> 0.0d");
        System.out.println("  boolean -> false");
        System.out.println("  char    -> '\\u0000'");
        System.out.println("  reference -> null");
        System.out.println();
    }
    
    /**
     * 解析阶段(Resolve)
     */
    private static void resolveDemo() {
        System.out.println("【3. 解析阶段】\n");
        
        /*
        解析阶段任务:
        将常量池内的符号引用替换为直接引用
        
        符号引用(Symbolic Reference):
        - 用一组符号描述引用的目标
        - 与虚拟机实现的内存布局无关
        
        直接引用(Direct Reference):
        - 直接指向目标的指针
        - 相对偏移量
        - 能定位到目标的句柄
        */
        
        System.out.println("符号引用 vs 直接引用:\n");
        
        System.out.println("符号引用:");
        System.out.println("  - 字符串形式的引用");
        System.out.println("  - 例: java/lang/System.out:Ljava/io/PrintStream;");
        System.out.println();
        
        System.out.println("直接引用:");
        System.out.println("  - 内存地址或偏移量");
        System.out.println("  - 例: 0x7fff1234 (内存地址)");
        System.out.println();
        
        System.out.println("解析类型:");
        System.out.println("  1. 类或接口解析");
        System.out.println("  2. 字段解析");
        System.out.println("  3. 方法解析");
        System.out.println("  4. 接口方法解析");
        System.out.println();
        
        // 解析示例
        System.out.println("示例:");
        System.out.println("  String str = new String();");
        System.out.println("  // 解析前: str是符号引用 'java/lang/String'");
        System.out.println("  // 解析后: str是指向String类对象的直接引用");
        System.out.println();
    }
}

/**
 * 准备阶段示例
 */
class PrepareExample {
    // 准备阶段: value = 0
    // 初始化阶段: value = 123
    public static int value = 123;
    
    // 准备阶段: CONST = 456 (final直接赋值)
    public static final int CONST = 456;
    
    // 准备阶段: obj = null
    // 初始化阶段: obj = new Object()
    public static Object obj = new Object();
}
2.1.3 初始化(Initialization)
/**
 * 初始化阶段详解
 * 
 * 初始化阶段是执行类构造器<clinit>()方法的过程
 */
public class InitializationDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 初始化阶段 ===\n");
        
        clinitDemo();
        initTriggerDemo();
        clinitRules();
    }
    
    /**
     * <clinit>方法示例
     */
    private static void clinitDemo() {
        System.out.println("【<clinit>方法】\n");
        
        /*
        <clinit>方法特点:
        
        1. 由编译器自动收集类中的所有类变量的赋值动作
           和静态语句块(static{})中的语句合并产生
        
        2. 收集顺序由源文件中出现的顺序决定
        
        3. 静态语句块只能访问定义在它之前的变量
        
        4. 先执行父类的<clinit>,再执行子类的<clinit>
        
        5. 如果类没有静态变量和静态代码块,则不生成<clinit>
        
        6. <clinit>方法是线程安全的
        */
        
        System.out.println("<clinit>方法生成规则:");
        System.out.println("  - 收集所有静态变量赋值");
        System.out.println("  - 收集所有静态代码块");
        System.out.println("  - 按源码顺序合并");
        System.out.println();
        
        System.out.println("示例代码:");
        System.out.println("  public static int a = 1;");
        System.out.println("  static { a = 2; }");
        System.out.println("  public static int b = 3;");
        System.out.println();
        
        System.out.println("<clinit>方法内容:");
        System.out.println("  a = 1;");
        System.out.println("  a = 2;  // 静态代码块");
        System.out.println("  b = 3;");
        System.out.println("  // 最终 a=2, b=3");
        System.out.println();
    }
    
    /**
     * 初始化触发条件
     */
    private static void initTriggerDemo() {
        System.out.println("【初始化触发条件】\n");
        
        /*
        主动使用(会触发初始化):
        
        1. 遇到new、getstatic、putstatic、invokestatic指令
           - new关键字实例化对象
           - 读取/设置静态字段(除final)
           - 调用静态方法
        
        2. 使用java.lang.reflect包反射调用
        
        3. 初始化类的子类时,父类先初始化
        
        4. 虚拟机启动时的主类(main方法所在类)
        
        5. MethodHandle/VarHandle解析时
        
        被动使用(不会触发初始化):
        - 通过子类引用父类的静态字段
        - 通过数组定义引用类
        - 引用常量(final static)
        */
        
        System.out.println("主动使用(触发初始化):");
        System.out.println("  1. new关键字");
        System.out.println("  2. 访问静态变量(非final)");
        System.out.println("  3. 调用静态方法");
        System.out.println("  4. 反射调用");
        System.out.println("  5. 初始化子类");
        System.out.println("  6. 主类(main方法所在)");
        System.out.println();
        
        System.out.println("被动使用(不触发初始化):");
        System.out.println("  1. 子类引用父类静态变量");
        System.out.println("  2. 数组定义引用类");
        System.out.println("  3. 引用final常量");
        System.out.println();
    }
    
    /**
     * 初始化规则示例
     */
    private static void clinitRules() {
        System.out.println("【初始化规则示例】\n");
        
        // 规则1: 父类先初始化
        System.out.println("规则1: 父类先于子类初始化");
        // new ChildClass();  // 会触发ParentClass初始化
        System.out.println();
        
        // 规则2: final常量不触发初始化
        System.out.println("规则2: final常量不触发初始化");
        System.out.println("  System.out.println(ConstClass.HELLO);");
        System.out.println("  // 不会触发ConstClass初始化");
        System.out.println();
        
        // 规则3: 数组不触发初始化
        System.out.println("规则3: 数组定义不触发初始化");
        System.out.println("  InitClass[] arr = new InitClass[10];");
        System.out.println("  // 不会触发InitClass初始化");
        System.out.println();
    }
}

// 示例类
class ParentClass {
    static {
        System.out.println("ParentClass 初始化");
    }
}

class ChildClass extends ParentClass {
    static {
        System.out.println("ChildClass 初始化");
    }
}

class ConstClass {
    static {
        System.out.println("ConstClass 初始化");
    }
    
    public static final String HELLO = "Hello";
}

class InitClass {
    static {
        System.out.println("InitClass 初始化");
    }
}

/**
 * 初始化顺序完整示例
 */
class InitializationOrder {
    // 静态变量
    private static int staticVar = print("静态变量");
    
    // 实例变量
    private int instanceVar = print("实例变量");
    
    // 静态代码块
    static {
        print("静态代码块");
    }
    
    // 实例代码块
    {
        print("实例代码块");
    }
    
    // 构造方法
    public InitializationOrder() {
        print("构造方法");
    }
    
    private static int print(String msg) {
        System.out.println(msg);
        return 0;
    }
    
    public static void main(String[] args) {
        System.out.println("=== 创建第一个对象 ===");
        new InitializationOrder();
        
        System.out.println("\n=== 创建第二个对象 ===");
        new InitializationOrder();
    }
    
    /*
    执行结果:
    静态变量
    静态代码块
    === 创建第一个对象 ===
    实例变量
    实例代码块
    构造方法
    
    === 创建第二个对象 ===
    实例变量
    实例代码块
    构造方法
    
    说明:静态变量和静态代码块只执行一次
    */
}

2.2 类加载器

/**
 * 类加载器详解
 */
public class ClassLoaderDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 类加载器 ===\n");
        
        showClassLoaderHierarchy();
        customClassLoader();
        classLoaderMethods();
    }
    
    /**
     * 类加载器层次结构
     */
    private static void showClassLoaderHierarchy() {
        System.out.println("【类加载器层次结构】\n");
        
        /*
        ┌─────────────────────────────────────────────────────────┐
        │                   类加载器层次结构                        │
        ├─────────────────────────────────────────────────────────┤
        │                                                         │
        │  ┌─────────────────────────────────────────────────┐   │
        │  │          Bootstrap ClassLoader                   │   │
        │  │          (启动类加载器)                           │   │
        │  │                                                   │   │
        │  │  加载: JAVA_HOME/lib/rt.jar, resources.jar等     │   │
        │  │  实现: C++实现,Java中为null                      │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │ extends                       │
        │  ┌──────────────────────▼──────────────────────────┐   │
        │  │          Extension ClassLoader                   │   │
        │  │          (扩展类加载器) [JDK 8]                   │   │
        │  │          Platform ClassLoader                    │   │
        │  │          (平台类加载器) [JDK 9+]                  │   │
        │  │                                                   │   │
        │  │  加载: JAVA_HOME/lib/ext/*.jar                   │   │
        │  │       或 java.ext.dirs 指定的目录                │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │ extends                       │
        │  ┌──────────────────────▼──────────────────────────┐   │
        │  │          Application ClassLoader                 │   │
        │  │          (应用程序类加载器)                       │   │
        │  │                                                   │   │
        │  │  加载: CLASSPATH下的类                           │   │
        │  │       用户自定义的类                              │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │ extends                       │
        │  ┌──────────────────────▼──────────────────────────┐   │
        │  │          Custom ClassLoader                      │   │
        │  │          (自定义类加载器)                         │   │
        │  │                                                   │   │
        │  │  如: Tomcat、OSGi、Spring等框架实现              │   │
        │  └─────────────────────────────────────────────────┘   │
        │                                                         │
        └─────────────────────────────────────────────────────────┘
        */
        
        // 获取当前类的类加载器
        ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
        System.out.println("当前类加载器: " + appClassLoader);
        System.out.println("  类型: " + appClassLoader.getClass().getName());
        
        // 获取父加载器
        ClassLoader extClassLoader = appClassLoader.getParent();
        System.out.println("\n父加载器: " + extClassLoader);
        System.out.println("  类型: " + extClassLoader.getClass().getName());
        
        // 获取启动类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println("\n启动类加载器: " + bootstrapClassLoader);
        System.out.println("  (C++实现,Java中表示为null)");
        
        // 查看各类加载器加载的类
        System.out.println("\n【各类加载器加载的类】\n");
        
        // 核心类由启动类加载器加载
        Class<?> stringClass = String.class;
        System.out.println("String类加载器: " + stringClass.getClassLoader());
        System.out.println("  (null = Bootstrap ClassLoader)");
        
        // 扩展类
        Class<?> curveClass = sun.security.ec.CurveDB.class;
        System.out.println("\nCurveDB类加载器: " + curveClass.getClassLoader());
        
        // 应用类
        System.out.println("\n当前类加载器: " + ClassLoaderDemo.class.getClassLoader());
        
        System.out.println();
    }
    
    /**
     * 类加载器关键方法
     */
    private static void classLoaderMethods() {
        System.out.println("【类加载器关键方法】\n");
        
        System.out.println("ClassLoader核心方法:");
        System.out.println();
        
        System.out.println("1. loadClass(String name)");
        System.out.println("   - 加载指定名称的类");
        System.out.println("   - 遵循双亲委派模型");
        System.out.println();
        
        System.out.println("2. findClass(String name)");
        System.out.println("   - 查找类的字节码");
        System.out.println("   - 自定义类加载器重写此方法");
        System.out.println();
        
        System.out.println("3. defineClass(String name, byte[] b, int off, int len)");
        System.out.println("   - 将字节数组转换为Class对象");
        System.out.println("   - 必须在findClass中调用");
        System.out.println();
        
        System.out.println("4. findLoadedClass(String name)");
        System.out.println("   - 检查类是否已加载");
        System.out.println("   - 在loadClass开头调用");
        System.out.println();
        
        System.out.println("5. getParent()");
        System.out.println("   - 获取父类加载器");
        System.out.println();
    }
    
    /**
     * 自定义类加载器
     */
    private static void customClassLoader() {
        System.out.println("【自定义类加载器示例】\n");
        
        /*
        自定义类加载器步骤:
        1. 继承ClassLoader类
        2. 重写findClass方法
        3. 在findClass中读取类字节码
        4. 调用defineClass生成Class对象
        */
        
        System.out.println("自定义类加载器用途:");
        System.out.println("  1. 从非标准来源加载类(网络、数据库)");
        System.out.println("  2. 实现类的隔离(同一类多版本共存)");
        System.out.println("  3. 实现热部署");
        System.out.println("  4. 加密解密类文件");
        System.out.println();
    }
}

/**
 * 自定义类加载器示例
 */
class CustomClassLoader extends ClassLoader {
    
    private String classPath;
    
    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }
    
    public CustomClassLoader(ClassLoader parent, String classPath) {
        super(parent);
        this.classPath = classPath;
    }
    
    /**
     * 重写findClass方法
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 读取类文件的字节码
            byte[] data = loadClassData(name);
            
            // 将字节码转换为Class对象
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    
    /**
     * 从文件读取类字节码
     */
    private byte[] loadClassData(String className) throws Exception {
        String fileName = classPath + className.replace('.', '/') + ".class";
        
        java.io.FileInputStream fis = new java.io.FileInputStream(fileName);
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        
        byte[] buffer = new byte[1024];
        int length;
        while ((length = fis.read(buffer)) != -1) {
            baos.write(buffer, 0, length);
        }
        
        fis.close();
        return baos.toByteArray();
    }
    
    /**
     * 测试自定义类加载器
     */
    public static void main(String[] args) throws Exception {
        // 创建自定义类加载器
        CustomClassLoader loader = new CustomClassLoader("/tmp/classes/");
        
        // 加载类
        Class<?> clazz = loader.loadClass("com.example.TestClass");
        
        System.out.println("类名: " + clazz.getName());
        System.out.println("类加载器: " + clazz.getClassLoader());
    }
}

2.3 双亲委派模型

/**
 * 双亲委派模型详解
 */
public class ParentDelegationDemo {
    
    public static void main(String[] args) {
        System.out.println("=== 双亲委派模型 ===\n");
        
        explainModel();
        showModelCode();
        breakModel();
        advantages();
    }
    
    /**
     * 双亲委派模型解释
     */
    private static void explainModel() {
        System.out.println("【双亲委派模型】\n");
        
        /*
        双亲委派模型工作流程:
        
        1. 类加载器收到类加载请求
        
        2. 首先把请求委派给父类加载器
        
        3. 父类加载器反馈无法完成(未找到类)时
        
        4. 子类加载器才会尝试自己加载
        
        流程图:
        
        ┌─────────────────────────────────────────────────────┐
        │                                                     │
        │   Application ClassLoader                           │
        │   收到加载请求                                       │
        │         │                                           │
        │         │ 1.委派给父加载器                           │
        │         ▼                                           │
        │   Extension ClassLoader                             │
        │         │                                           │
        │         │ 2.委派给父加载器                           │
        │         ▼                                           │
        │   Bootstrap ClassLoader                             │
        │         │                                           │
        │         │ 3.尝试加载                                 │
        │         │                                           │
        │   ┌─────┴─────┐                                    │
        │   │ 找到?     │                                    │
        │   └─────┬─────┘                                    │
        │    是 ↙   ↘ 否                                      │
        │   返回      继续向下                                │
        │              │                                      │
        │         Extension ClassLoader                      │
        │              │                                      │
        │         ┌────┴────┐                                │
        │         │ 找到?   │                                │
        │         └────┬────┘                                │
        │      是 ↙   ↘ 否                                    │
        │     返回      继续向下                              │
        │                │                                    │
        │           Application ClassLoader                  │
        │                │                                    │
        │           ┌────┴────┐                              │
        │           │ 找到?   │                              │
        │           └────┬────┘                              │
        │        是 ↙   ↘ 否                                  │
        │       返回    抛出ClassNotFoundException             │
        │                                                     │
        └─────────────────────────────────────────────────────┘
        */
        
        System.out.println("双亲委派模型:");
        System.out.println("  1. 收到加载请求先委派给父加载器");
        System.out.println("  2. 父加载器无法完成时,子加载器才尝试");
        System.out.println("  3. 保证类的唯一性和安全性");
        System.out.println();
    }
    
    /**
     * 双亲委派模型代码实现
     */
    private static void showModelCode() {
        System.out.println("【ClassLoader.loadClass实现】\n");
        
        String code = """
        protected Class<?> loadClass(String name, boolean resolve) {
            synchronized (getClassLoadingLock(name)) {
                // 1. 检查类是否已加载
                Class<?> c = findLoadedClass(name);
                
                if (c == null) {
                    try {
                        if (parent != null) {
                            // 2. 委派给父加载器
                            c = parent.loadClass(name, false);
                        } else {
                            // 3. 没有父加载器,使用启动类加载器
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // 父加载器无法完成,捕获异常
                    }
                    
                    if (c == null) {
                        // 4. 父加载器无法完成,自己尝试加载
                        c = findClass(name);
                    }
                }
                
                if (resolve) {
                    resolveClass(c);
                }
                
                return c;
            }
        }
        """;
        
        System.out.println(code);
        System.out.println();
    }
    
    /**
     * 打破双亲委派模型
     */
    private static void breakModel() {
        System.out.println("【打破双亲委派模型的场景】\n");
        
        System.out.println("1. JDK 1.2之前(无双亲委派)");
        System.out.println("   - 用户继承ClassLoader并重写loadClass");
        System.out.println();
        
        System.out.println("2. SPI机制(Service Provider Interface)");
        System.out.println("   - JDBC: java.sql.Driver由启动类加载器加载");
        System.out.println("   - 实现: mysql-connector等由应用类加载器加载");
        System.out.println("   - 解决: Thread.setContextClassLoader()");
        System.out.println();
        
        System.out.println("3. OSGi、Jigsaw等模块化系统");
        System.out.println("   - 类加载器形成网状结构");
        System.out.println("   - 每个模块有自己的类加载器");
        System.out.println();
        
        System.out.println("4. Tomcat等Web容器");
        System.out.println("   - 每个WebApp独立的类加载器");
        System.out.println("   - 优先加载WebApp自己的类");
        System.out.println();
        
        // 打破双亲委派的示例代码
        System.out.println("打破双亲委派的代码模式:");
        System.out.println("""
            @Override
            protected Class<?> loadClass(String name, boolean resolve) {
                // 1. 先检查已加载
                Class<?> c = findLoadedClass(name);
                
                if (c == null) {
                    // 2. 某些类需要特殊处理
                    if (name.startsWith("com.myapp.")) {
                        // 自己先加载,不委派给父加载器
                        c = findClass(name);
                    } else {
                        // 其他类走双亲委派
                        c = super.loadClass(name, resolve);
                    }
                }
                
                return c;
            }
        """);
        System.out.println();
    }
    
    /**
     * 双亲委派模型的优点
     */
    private static void advantages() {
        System.out.println("【双亲委派模型的优点】\n");
        
        System.out.println("1. 安全性");
        System.out.println("   - 核心类由启动类加载器加载");
        System.out.println("   - 用户无法伪造核心类(如java.lang.String)");
        System.out.println();
        
        System.out.println("2. 唯一性");
        System.out.println("   - 同一个类只会被加载一次");
        System.out.println("   - 所有类加载器看到的都是同一个类");
        System.out.println();
        
        System.out.println("3. 一致性");
        System.out.println("   - 避免类的重复加载");
        System.out.println("   - 保证Java程序稳定运行");
        System.out.println();
        
        // 演示无法伪造核心类
        System.out.println("【演示:无法伪造核心类】\n");
        
        // 尝试定义java.lang.String
        System.out.println("尝试创建 java.lang.MyString:");
        System.out.println("  即使创建成功,也会由启动类加载器加载");
        System.out.println("  不会替换核心类 java.lang.String");
        System.out.println();
        
        // 禁止自定义java.lang包下的类
        System.out.println("禁止定义java.开头的包:");
        System.out.println("  java.lang.SecurityException: Prohibited package name: java.lang");
        System.out.println();
    }
}

/**
 * 演示双亲委派保证类的唯一性
 */
class ClassUniquenessDemo {
    public static void main(String[] args) {
        // 不同类加载器加载同一个类
        ClassLoader appLoader = ClassUniquenessDemo.class.getClassLoader();
        
        // 获取String类(由启动类加载器加载)
        Class<?> stringClass1 = String.class;
        Class<?> stringClass2 = "hello".getClass();
        
        System.out.println("String.class == \"hello\".getClass(): " + 
            (stringClass1 == stringClass2));  // true
        
        System.out.println("类加载器: " + stringClass1.getClassLoader());  // null (Bootstrap)
        
        // 同一个类由不同类加载器加载,是不同的类
        System.out.println("\n注意:");
        System.out.println("  同一个Class文件,不同类加载器加载 -> 不同的Class对象");
        System.out.println("  instanceof比较返回false");
    }
}

2.4 打破双亲委派模型

import java.io.*;

/**
 * 打破双亲委派模型示例
 * 实现类的隔离和热加载
 */
public class BreakParentDelegationDemo {
    
    public static void main(String[] args) throws Exception {
        System.out.println("=== 打破双亲委派模型示例 ===\n");
        
        // 1. 实现类隔离
        classIsolation();
        
        // 2. 实现热加载
        hotReload();
    }
    
    /**
     * 类隔离示例:同一类多版本共存
     */
    private static void classIsolation() throws Exception {
        System.out.println("【类隔离示例】\n");
        
        // 创建两个不同的类加载器
        IsolatingClassLoader loader1 = new IsolatingClassLoader("/version1/");
        IsolatingClassLoader loader2 = new IsolatingClassLoader("/version2/");
        
        // 加载同一个类名,但内容不同
        Class<?> clazz1 = loader1.loadClass("com.example.VersionClass");
        Class<?> clazz2 = loader2.loadClass("com.example.VersionClass");
        
        System.out.println("类名相同: " + clazz1.getName() + " == " + clazz2.getName());
        System.out.println("Class对象相同: " + (clazz1 == clazz2));  // false
        
        System.out.println("\n说明:");
        System.out.println("  同名类由不同类加载器加载,是不同的类");
        System.out.println("  可实现同一应用内多版本共存");
        System.out.println();
        
        /*
        应用场景:
        - Tomcat: 多个WebApp使用不同版本的库
        - OSGi: 每个Bundle有自己的类加载器
        - 插件系统: 插件间相互隔离
        */
    }
    
    /**
     * 热加载示例
     */
    private static void hotReload() throws Exception {
        System.out.println("【热加载示例】\n");
        
        // 加载类并执行
        HotReloadingClassLoader loader = new HotReloadingClassLoader("/hotload/");
        
        Class<?> clazz = loader.loadClass("com.example.HotClass");
        Object instance = clazz.newInstance();
        java.lang.reflect.Method method = clazz.getMethod("sayHello");
        method.invoke(instance);
        
        System.out.println("\n修改类文件后,创建新的类加载器...");
        
        // 创建新的类加载器加载修改后的类
        HotReloadingClassLoader newLoader = new HotReloadingClassLoader("/hotload/");
        Class<?> newClazz = newLoader.loadClass("com.example.HotClass");
        Object newInstance = newClazz.newInstance();
        java.lang.reflect.Method newMethod = newClazz.getMethod("sayHello");
        newMethod.invoke(newInstance);
        
        System.out.println("\n说明:");
        System.out.println("  每次创建新的类加载器,可加载修改后的类");
        System.out.println("  实现代码修改后无需重启应用");
        System.out.println();
    }
}

/**
 * 隔离类加载器:打破双亲委派
 */
class IsolatingClassLoader extends ClassLoader {
    
    private String classPath;
    
    public IsolatingClassLoader(String classPath) {
        this.classPath = classPath;
    }
    
    /**
     * 重写loadClass,打破双亲委派
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
            throws ClassNotFoundException {
        
        synchronized (getClassLoadingLock(name)) {
            // 1. 检查是否已加载
            Class<?> c = findLoadedClass(name);
            
            if (c == null) {
                // 2. java核心类还是委派给父加载器
                if (name.startsWith("java.")) {
                    c = super.loadClass(name, resolve);
                } else {
                    // 3. 其他类自己先加载(打破双亲委派)
                    try {
                        c = findClass(name);
                    } catch (ClassNotFoundException e) {
                        // 4. 找不到再委派给父加载器
                        c = super.loadClass(name, resolve);
                    }
                }
            }
            
            if (resolve) {
                resolveClass(c);
            }
            
            return c;
        }
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadClassData(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    
    private byte[] loadClassData(String className) throws IOException {
        String fileName = classPath + className.replace('.', '/') + ".class";
        try (FileInputStream fis = new FileInputStream(fileName);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        }
    }
}

/**
 * 热加载类加载器
 */
class HotReloadingClassLoader extends ClassLoader {
    
    private String classPath;
    
    public HotReloadingClassLoader(String classPath) {
        this.classPath = classPath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadClassData(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    
    private byte[] loadClassData(String className) throws IOException {
        String fileName = classPath + className.replace('.', '/') + ".class";
        try (FileInputStream fis = new FileInputStream(fileName);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        }
    }
}

三、可运行Java代码示例

完整示例:SPI机制与线程上下文类加载器

import java.sql.*;
import java.util.*;

/**
 * SPI机制示例
 * Service Provider Interface - 服务提供者接口
 */
public class SPIDemo {
    
    public static void main(String[] args) {
        System.out.println("=== SPI机制详解 ===\n");
        
        explainSPI();
        contextClassLoaderDemo();
        implementSPI();
    }
    
    /**
     * SPI机制解释
     */
    private static void explainSPI() {
        System.out.println("【SPI机制】\n");
        
        /*
        SPI (Service Provider Interface):
        - 定义接口在核心库中(由启动类加载器加载)
        - 实现类由第三方提供(由应用类加载器加载)
        - 打破双亲委派模型的经典场景
        
        典型SPI:
        - JDBC: java.sql.Driver (接口) -> mysql-connector (实现)
        - JNDI: javax.naming.spi.InitialContextFactory
        - JAXP: javax.xml.parsers.DocumentBuilderFactory
        - JAXB: javax.xml.bind.JAXBContext
        */
        
        System.out.println("SPI问题:");
        System.out.println("  接口: java.sql.Driver (启动类加载器)");
        System.out.println("  实现: com.mysql.jdbc.Driver (应用类加载器)");
        System.out.println();
        System.out.println("问题:");
        System.out.println("  启动类加载器无法「向下」加载应用类加载器的类");
        System.out.println();
        System.out.println("解决方案:");
        System.out.println("  线程上下文类加载器 (Context ClassLoader)");
        System.out.println();
    }
    
    /**
     * 线程上下文类加载器
     */
    private static void contextClassLoaderDemo() {
        System.out.println("【线程上下文类加载器】\n");
        
        // 获取当前线程的上下文类加载器
        Thread thread = Thread.currentThread();
        ClassLoader contextLoader = thread.getContextClassLoader();
        
        System.out.println("当前线程: " + thread.getName());
        System.out.println("上下文类加载器: " + contextLoader);
        System.out.println("类加载器类型: " + contextLoader.getClass().getName());
        System.out.println();
        
        /*
        上下文类加载器的作用:
        
        1. 启动类加载器加载的类需要访问应用类加载器加载的类
        
        2. JDK核心类库通过线程上下文类加载器加载SPI实现
        
        3. 默认情况下,上下文类加载器是应用类加载器
        
        代码示例(JDBC):
        
        // DriverManager (启动类加载器)
        public class DriverManager {
            static {
                // 使用上下文类加载器加载Driver实现
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        ServiceLoader<Driver> loadedDrivers = 
                            ServiceLoader.load(Driver.class);
                        // ...
                    }
                });
            }
        }
        
        // ServiceLoader.load
        public static <S> ServiceLoader<S> load(Class<S> service) {
            // 使用上下文类加载器
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return new ServiceLoader<>(service, cl);
        }
        */
        
        System.out.println("SPI加载流程:");
        System.out.println("  1. DriverManager由启动类加载器加载");
        System.out.println("  2. DriverManager使用ServiceLoader加载Driver");
        System.out.println("  3. ServiceLoader使用线程上下文类加载器");
        System.out.println("  4. 上下文类加载器是应用类加载器");
        System.out.println("  5. 成功加载mysql-connector的Driver实现");
        System.out.println();
    }
    
    /**
     * 实现自定义SPI
     */
    private static void implementSPI() {
        System.out.println("【自定义SPI示例】\n");
        
        // 使用ServiceLoader加载服务实现
        ServiceLoader<SPIDemoService> services = 
            ServiceLoader.load(SPIDemoService.class);
        
        System.out.println("加载 SPI 实现类:");
        for (SPIDemoService service : services) {
            System.out.println("  - " + service.getClass().getName());
            service.execute();
        }
        
        System.out.println();
        System.out.println("SPI配置文件位置:");
        System.out.println("  META-INF/services/完整接口名");
        System.out.println();
        System.out.println("示例:");
        System.out.println("  文件: META-INF/services/com.example.SPIDemoService");
        System.out.println("  内容: com.example.impl.SPIDemoServiceImpl");
        System.out.println();
    }
}

/**
 * SPI接口
 */
interface SPIDemoService {
    void execute();
}

/**
 * SPI实现类1
 */
class SPIDemoServiceImpl1 implements SPIDemoService {
    @Override
    public void execute() {
        System.out.println("    SPIDemoServiceImpl1 executed");
    }
}

/**
 * SPI实现类2
 */
class SPIDemoServiceImpl2 implements SPIDemoService {
    @Override
    public void execute() {
        System.out.println("    SPIDemoServiceImpl2 executed");
    }
}

完整示例:Tomcat类加载机制

/**
 * Tomcat类加载机制示例
 */
public class TomcatClassLoaderDemo {
    
    public static void main(String[] args) {
        System.out.println("=== Tomcat类加载机制 ===\n");
        
        showHierarchy();
        explainLoadingOrder();
    }
    
    /**
     * Tomcat类加载器层次
     */
    private static void showHierarchy() {
        System.out.println("【Tomcat类加载器层次】\n");
        
        /*
        ┌─────────────────────────────────────────────────────────┐
        │                   Tomcat类加载器层次                      │
        ├─────────────────────────────────────────────────────────┤
        │                                                         │
        │  ┌─────────────────────────────────────────────────┐   │
        │  │          Bootstrap ClassLoader                   │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │                               │
        │  ┌──────────────────────▼──────────────────────────┐   │
        │  │          System ClassLoader                      │   │
        │  │          (加载Tomcat核心类)                       │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │                               │
        │  ┌──────────────────────▼──────────────────────────┐   │
        │  │          Common ClassLoader                      │   │
        │  │          (加载Tomcat和WebApp共享类)               │   │
        │  └──────────────────────┬──────────────────────────┘   │
        │                         │                               │
        │  ┌──────────────────────┴──────────────────────────┐   │
        │  │                                                   │   │
        │  │  ┌─────────────────┐    ┌─────────────────┐    │   │
        │  │  │ Catalina        │    │ Shared          │    │   │
        │  │  │ ClassLoader     │    │ ClassLoader     │    │   │
        │  │  └─────────────────┘    └────────┬────────┘    │   │
        │  │                                   │              │   │
        │  └───────────────────────────────────┼──────────────┘   │
        │                                      │                  │
        │  ┌───────────────────────────────────▼──────────────┐   │
        │  │              WebApp ClassLoader                   │   │
        │  │              (每个WebApp一个)                      │   │
        │  │                                                   │   │
        │  │  ┌─────────────┐  ┌─────────────┐  ┌───────────┐│   │
        │  │  │ WebApp1     │  │ WebApp2     │  │ WebApp3   ││   │
        │  │  │ ClassLoader │  │ ClassLoader │  │ ClassLoad ││   │
        │  │  └─────────────┘  └─────────────┘  └───────────┘│   │
        │  │                                                   │   │
        │  └───────────────────────────────────────────────────┘   │
        │                                                         │
        └─────────────────────────────────────────────────────────┘
        */
        
        System.out.println("各加载器职责:");
        System.out.println();
        System.out.println("Bootstrap ClassLoader:");
        System.out.println("  加载JVM核心类库");
        System.out.println();
        System.out.println("System ClassLoader:");
        System.out.println("  加载Tomcat启动类(catalina.sh指定的类)");
        System.out.println();
        System.out.println("Common ClassLoader:");
        System.out.println("  加载Tomcat和WebApp共享的类(如Servlet API)");
        System.out.println();
        System.out.println("Catalina ClassLoader:");
        System.out.println("  加载Tomcat内部实现类(WebApp不可见)");
        System.out.println();
        System.out.println("Shared ClassLoader:");
        System.out.println("  加载所有WebApp共享的类");
        System.out.println();
        System.out.println("WebApp ClassLoader:");
        System.out.println("  每个WebApp独立,加载WebApp自己的类");
        System.out.println("  打破双亲委派,优先加载WebApp类");
        System.out.println();
    }
    
    /**
     * Tomcat类加载顺序
     */
    private static void explainLoadingOrder() {
        System.out.println("【WebApp类加载顺序】\n");
        
        System.out.println("默认加载顺序(打破双亲委派):");
        System.out.println("  1. JVM Bootstrap类");
        System.out.println("  2. WebApp自己类库 (WEB-INF/classes)");
        System.out.println("  3. WebApp依赖库 (WEB-INF/lib)");
        System.out.println("  4. Tomcat共享类库");
        System.out.println("  5. System类库");
        System.out.println();
        
        System.out.println("特点:");
        System.out.println("  - WebApp类优先于Tomcat共享类");
        System.out.println("  - 不同WebApp可以使用不同版本的库");
        System.out.println("  - 实现WebApp之间的类隔离");
        System.out.println();
        
        System.out.println("配置修改:");
        System.out.println("  delegate=\"true\": 遵循双亲委派");
        System.out.println("  delegate=\"false\": 打破双亲委派(默认)");
        System.out.println();
        
        System.out.println("配置示例 (context.xml):");
        System.out.println("""
            <Context>
              <Loader className="org.apache.catalina.loader.WebappLoader"
                      delegate="false"/>
            </Context>
        """);
        System.out.println();
    }
}

四、实战应用场景

场景1:插件化架构

import java.io.*;
import java.util.*;

/**
 * 插件化架构实现
 */
public class PluginDemo {
    
    public static void main(String[] args) throws Exception {
        System.out.println("=== 插件化架构 ===\n");
        
        // 创建插件管理器
        PluginManager manager = new PluginManager();
        
        // 加载插件
        manager.loadPlugin("/plugins/plugin1.jar");
        manager.loadPlugin("/plugins/plugin2.jar");
        
        // 执行所有插件
        manager.executeAllPlugins();
        
        // 卸载插件
        manager.unloadPlugin("plugin1");
    }
}

/**
 * 插件接口
 */
interface Plugin {
    String getName();
    void execute();
}

/**
 * 插件管理器
 */
class PluginManager {
    
    private Map<String, PluginClassLoader> plugins = new HashMap<>();
    
    /**
     * 加载插件
     */
    public void loadPlugin(String jarPath) throws Exception {
        PluginClassLoader loader = new PluginClassLoader(jarPath);
        
        // 读取插件配置
        // META-INF/services/Plugin
        ServiceLoader<Plugin> services = ServiceLoader.load(Plugin.class, loader);
        
        for (Plugin plugin : services) {
            String name = plugin.getName();
            plugins.put(name, loader);
            System.out.println("加载插件: " + name);
        }
    }
    
    /**
     * 执行所有插件
     */
    public void executeAllPlugins() {
        for (Map.Entry<String, PluginClassLoader> entry : plugins.entrySet()) {
            PluginClassLoader loader = entry.getValue();
            ServiceLoader<Plugin> services = 
                ServiceLoader.load(Plugin.class, loader);
            
            for (Plugin plugin : services) {
                System.out.println("执行插件: " + plugin.getName());
                plugin.execute();
            }
        }
    }
    
    /**
     * 卸载插件
     */
    public void unloadPlugin(String name) {
        PluginClassLoader loader = plugins.remove(name);
        if (loader != null) {
            // 卸载类加载器,允许GC回收类
            loader = null;
            System.out.println("卸载插件: " + name);
        }
    }
}

/**
 * 插件类加载器
 */
class PluginClassLoader extends ClassLoader {
    
    private String jarPath;
    
    public PluginClassLoader(String jarPath) {
        this.jarPath = jarPath;
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从JAR中读取类字节码并定义类
        // 实现略
        throw new ClassNotFoundException(name);
    }
}

五、总结与最佳实践

核心要点回顾

阶段任务
加载读取Class文件,创建Class对象
验证校验Class文件格式、语义、字节码
准备为静态变量分配内存,设置默认值
解析将符号引用转换为直接引用
初始化执行<clinit>方法,初始化静态变量

类加载器分类

加载器加载内容说明
BootstrapJAVA_HOME/lib/*.jarC++实现
Extension/PlatformJAVA_HOME/lib/ext/*.jarJava实现
ApplicationCLASSPATH下的类默认用户类加载器
Custom自定义位置框架自定义实现

最佳实践

  1. 遵循双亲委派

    • 除非必要,不要打破双亲委派模型
    • 优先使用标准类加载器
  2. 正确使用SPI

    // 使用ServiceLoader加载SPI实现
    ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
    
  3. 避免类加载泄漏

    • 及时释放自定义类加载器
    • ThreadLocal配合使用需注意清理
  4. 理解初始化顺序

    • 父类静态 -> 子类静态 -> 父类实例 -> 子类实例

常见问题

  1. ClassNotFoundException vs NoClassDefFoundError

    • ClassNotFoundException: 加载时找不到类
    • NoClassDefFoundError: 运行时找不到类(类存在但加载失败)
  2. 类加载死锁

    • 多线程并发加载类时可能发生
    • 解决:避免在静态初始化块中创建线程

相关JVM参数

# 类加载日志
-verbose:class                 # 打印类加载信息
-XX:+TraceClassLoading         # 跟踪类加载
-XX:+TraceClassUnloading       # 跟踪类卸载

# 类路径
-Xbootclasspath                # 启动类路径
-Xbootclasspath/a              # 追加启动类路径
-Djava.ext.dirs                # 扩展类路径
-Djava.class.path              # 应用类路径

扩展阅读

  • 《深入理解Java虚拟机》:周志明著,类加载机制章节
  • JSR 121:Java Application Isolation
  • OSGi规范:动态模块化系统
  • Tomcat源码:WebappClassLoader实现