JVM 类加载 详解

JVM 类加载详解

JVM 类加载(Java Class Loading) 是 Java 虚拟机 (JVM) 执行 Java 程序的重要机制之一,用于将 .class 文件动态加载到内存中并进行验证、解析和初始化,最终生成可以直接使用的类对象。


1. 类加载的基本概念

1.1 什么是类加载?

类加载是将 .class 文件加载到 JVM 并转化为内存中可以运行的类的过程。

  • 目标:生成一个内存中的 Class 对象,供程序使用。
  • 触发点
    • 当程序首次访问类或接口时(如创建对象、访问静态字段或调用静态方法)。
    • 通过反射加载类(如 Class.forName)。
    • 动态代理生成的类。

1.2 类加载的三阶段

  1. 加载(Loading):
    • .class 文件加载到内存中,生成 Class 对象。
  2. 链接(Linking):
    • 将类的二进制数据合并到 JVM 中。
      • 验证:检查类文件的合法性。
      • 准备:为类的静态变量分配内存,并设置默认初始值。
      • 解析:将类、方法、字段的符号引用解析为直接引用。
  3. 初始化(Initialization):
    • 执行类的初始化逻辑,包括静态变量的赋值和静态代码块的执行。

2. JVM 类加载机制

JVM 的类加载遵循 双亲委派模型运行时动态加载 的原则。

2.1 双亲委派模型

定义

类加载时会先委托父加载器加载,只有当父加载器无法加载时,才由当前加载器尝试加载。

过程
  1. 一个类加载请求从当前类加载器开始。
  2. 如果当前加载器存在父加载器,优先交给父加载器处理。
  3. 如果父加载器无法加载,则由当前加载器加载。
优点
  • 安全性:防止重复加载,保证核心类不被篡改。
  • 统一性:确保 Java 的基础类库(如 java.lang.String)使用同一个加载器加载。

2.2 类加载器

JVM 的主要类加载器
  1. 启动类加载器(Bootstrap ClassLoader)

    • 负责加载 JVM 的核心类库,如 rt.jar
    • 用 C/C++ 实现,直接由 JVM 内部调用。
  2. 扩展类加载器(Extension ClassLoader)

    • 加载 JAVA_HOME/lib/ext 目录下的扩展类库。
  3. 应用类加载器(App ClassLoader)

    • 加载应用程序的类路径(CLASSPATH)下的类。
  4. 自定义类加载器(Custom ClassLoader)

    • 用户可以通过继承 ClassLoader 自定义类加载器。
类加载器之间的关系
  • 启动类加载器是最顶层的加载器,扩展类加载器和应用类加载器是其子加载器。
  • 自定义类加载器通常由应用类加载器加载。

3. 类加载的流程

以下是类加载的主要流程及每个阶段的作用:

3.1 加载阶段

  • 功能:将 .class 文件加载到内存中,生成 Class 对象。
  • 操作
    1. 通过类的全限定名找到对应的 .class 文件。
    2. .class 文件的字节流读入内存。
    3. 将字节流解析为 Class 对象。

3.2 链接阶段

  1. 验证

    • 确保 .class 文件符合 Java 规范,保证字节码的合法性。
    • 检查:
      • 魔数和版本号。
      • 常量池中符号的正确性。
      • 字节码指令是否正确。
  2. 准备

    • 为静态变量分配内存,并设置默认初始值(如 0null)。
    • 示例:
      public static int a = 10;
      
      在准备阶段,a 的值为 0,赋值为 10 在初始化阶段完成。
  3. 解析

    • 将常量池中的符号引用(如方法名、字段名)替换为直接引用(内存地址)。

3.3 初始化阶段

  • 触发点
    • 主动使用类(如实例化对象、调用静态方法)。
  • 操作
    • 初始化静态变量。
    • 执行静态代码块。

4. 类加载的类型

根据触发类加载的时机,类加载分为以下两种类型:

4.1 主动引用

以下情况会触发类的加载:

  1. 创建类的实例。
  2. 访问类的静态字段。
  3. 调用类的静态方法。
  4. 通过反射调用类。
  5. 初始化类的子类。

示例:

class Demo {
    static {
        System.out.println("Demo类被初始化");
    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo(); // 主动触发
    }
}

4.2 被动引用

以下情况不会触发类的加载:

  1. 通过子类调用父类的静态字段。
  2. 定义类的数组。
  3. 引用类的常量。

示例:

class Parent {
    static {
        System.out.println("Parent类被初始化");
    }
    public static int value = 42;
}

class Child extends Parent {
    static {
        System.out.println("Child类被初始化");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Child.value); // 只触发父类加载
    }
}

5. 类加载的关键点

5.1 类加载器的双亲委派机制

  • 原理:父加载器优先加载,确保基础类不被重复加载。
  • 示例
ClassLoader loader = Demo.class.getClassLoader();
System.out.println(loader);                   // AppClassLoader
System.out.println(loader.getParent());       // ExtClassLoader
System.out.println(loader.getParent().getParent()); // null (Bootstrap)

5.2 自定义类加载器

  • 自定义类加载器通常用于加载加密的类文件或特殊格式的类文件。

实现示例

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String name) {
        // 加载类文件的字节流
        return new byte[0]; // 示例中省略实际加载逻辑
    }
}

6. 类加载的示例

6.1 静态字段与静态代码块

class Demo {
    static int value = 10;
    static {
        System.out.println("静态代码块执行");
        value = 20;
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println(Demo.value); // 输出: 静态代码块执行 20
    }
}

6.2 动态加载类

public class Main {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = Class.forName("com.example.Demo");
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj.getClass().getName());
    }
}

7. 类加载的优缺点

7.1 优点

  1. 延迟加载:提高系统启动效率。
  2. 动态扩展:支持动态加载类,增强灵活性。
  3. 隔离性:不同的类加载器可以隔离命名空间,避免冲突。

7.2 缺点

  1. 性能开销:加载类时需要额外的资源。
  2. 复杂性:双亲委派机制增加调试难度。

8. 总结

  • 核心机制:类加载是 JVM 将 .class 文件转化为可运行代码的关键环节,主要包括加载、链接和初始化三阶段。
  • 双亲委派模型:确保类加载的安全性和统一性。
  • 动态性:支持运行时加载和动态代理等功能。
  • 实际应用:常见于自定义类加载器、插件系统和框架设计中。

深入理解类加载机制,可以
帮助开发者更好地调优 JVM,设计更灵活的应用程序。

上一篇:运维工作常用Shell脚本(Commonly Used Shell Scripts for Operation and Maintenance Work)- ????????????欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。


下一篇:【Debug】hexo-github令牌认证 Support for password authentication was removed