wayne
wayne
Published on 2025-05-25 / 0 Visits
0
0

JAVA基础 - 类加载机制

类加载全流程

根据 JVM 规范,类生命周期分为以下阶段:

加载(Loading)

.class 字节码加载到内存,生成 Class<?> 对象。为数不多应用代码中可以介入插手的阶段。

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

类缓存机制

每个类加载器调用 loadClass() 时,首先通过 findLoadedClass() (native method)检查本地缓存是否已加载该类,避免重复加载。

双亲委派流程

方法ClassLoader.loadClass()parent.loadClass()findClass()

  • 向上委派:子类加载器的 loadClass() 优先调用父类加载器的 parent.loadClass()(递归至 Bootstrap)。

  • 向下加载:若所有父类均无法加载(返回 null),子类调用 findClass() 自行加载(如 AppClassLoaderclasspath 中搜索)。

沙箱保护机制

方法ClassLoader.loadClass() 的访问控制 + SecurityManager.checkPackageAccess()

  • 双亲委派机制的保护:通过双亲委派,Bootstrap 优先加载 java.* 等核心类,下级比如AppClassLoader 无权加载核心类

  • 权限限制:自定义类加载器尝试加载 java.* 包,defineClass() 会直接抛出 SecurityException(JVM 底层限制)。

SecurityException: Prohibited package name: java.lang
java.lang.SecurityException: Prohibited package name: java.lang
    at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:942)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
    at CustomClassLoader.findClass(CustomClassLoader.java:18)
    ...

loadClass,findClass 和 defineClass 速记

loadClass

加载类到内存中时,确定用哪一个ClassLoader 加载,是实现双亲委派的关键, 可以覆写从而打破双亲委派。

findClass

findClassClassLoader 的模板方法,用于 自定义类加载逻辑(如从非标准位置加载类)。

  • 默认行为ClassLoaderfindClass 直接抛出 ClassNotFoundException

  • 自定义类加载器 通常会重写 findClass,在其中:

    1. 查找类字节码(如从文件、网络、数据库等)。

    2. 调用 defineClass 将字节码转换为 Class 对象。

defineClass

defineClassClassLoader 的核心方法,负责将 字节码(byte[])转换为 Class 对象。通常在findClass 中调用。

链接(Linking)

链接分为三个子阶段:

验证(Verification)

  • 目标:确保字节码符合 JVM 规范(如类型安全、操作数栈合法性)。

  • 触发时机:在 defineClass 中完成(属于加载阶段的一部分,但规范将其归为链接阶段)。

  • 验证内容

    • 文件格式验证(魔数、版本号)。

    • 元数据验证(继承关系、final 规则)。

    • 字节码验证(方法逻辑、跳转指令)。

准备(Preparation)

  • 目标:为类的静态变量分配内存,并设置默认初始值(如 int 设为 0,引用设为 null)。作⽤是占住内存。然后等连接过程完成后,在后⾯的初始化阶段,再将静态属性从默认值修改为指定的初始值。

  • 触发时机:由 JVM 在链接阶段自动处理,无直接方法调用。

示例

public static int value = 123; // 准备阶段设为 0,初始化阶段设为 123
例外情况 - final

如果这个静态变量是final修饰的,准备阶段直接赋实际值(跳过零值)。

解析(Resolution)

  • 目标:将符号引用(如类名、方法名)转换为直接引用(内存地址或句柄)。

  • 触发方法

    • 延迟解析:默认由 JVM 在首次实际使用时触发(如调用方法、访问字段)。

    • 显式解析:通过 ClassLoader.resolveClass(Class<?> c) 提前触发。

示例

classLoader.resolveClass(MyClass.class); // 强制解析

初始化(Initialization)

  • 目标:执行类的静态代码块(<clinit>)和静态变量赋值。JVM 准备类的静态环境(一次性的)。

  • 触发条件(JVM 严格控制的主动使用场景):

    • new 实例化对象。

    • 访问类的静态变量(非 final)或静态方法。

    • 反射调用(如 Class.forName("MyClass"))。

    • 子类初始化时,父类需先初始化。

  • 核心任务

    • 执行静态变量赋值(如 static int x = 10;)。

    • 执行静态代码块(static { ... })。

    • 生成类的 Class 对象(Class<?>)。

  • 关键方法

    • Class.forName(String name, boolean initialize, ClassLoader loader):若 initialize=true 会触发初始化。

class Demo {
    static {
        System.out.println("静态代码块执行(初始化阶段)");
    }
    static int x = 1; // 静态变量赋值
}
// 触发初始化:
Demo.x = 2; // 访问静态变量

使用(Usage)

  • 目标:正常使用类的实例和静态成员。程序运行时的动态行为(可多次发生)。

  • 核心行为

    • 创建对象实例(调用构造函数 <init>)。

    • 访问普通成员变量或方法。

    • 通过反射操作类(如调用方法、读写字段)。

  • 触发方式:通过 new、方法调用、字段访问等。

class Demo {
    int y = 10; // 实例变量(初始化阶段不处理)
    void print() { System.out.println("方法调用(使用阶段)"); }
}
// 使用阶段:
Demo obj = new Demo(); // 创建实例
obj.print();           // 调用方法

卸载(Unloading)

  • 目标:从内存中移除不再使用的类。

  • 触发条件

    • 类的所有实例已被回收。

    • 加载该类的 ClassLoader 实例已被回收。

    • 无任何地方通过反射持有该类的引用。

  • 由 JVM 的垃圾回收机制自动处理,无直接 API 控制。


Comment