类加载机制

类加载机制

JVM 需要把类的字节流转换为运行时数据结构,并创建对应的 Class 对象。类的生命周期通常包括加载、连接、初始化、使用和卸载。

加载

加载阶段完成三件事:

  1. 根据全限定名获取二进制字节流。
  2. 转换为 JVM 的运行时数据结构。
  3. 创建代表该类型的 Class 对象。

字节流可以来自 JAR、目录、网络或动态生成。同一个类由“全限定名 + 定义它的类加载器”共同确定;名称相同但加载器不同,仍是不同类型。

连接

验证

检查字节码格式、元数据、类型和符号引用是否合法。

准备

为静态字段分配内存并设置类型默认值。例如 static int count = 10 通常先得到 0,赋值 10 在初始化阶段完成。编译期常量可能直接得到常量值。

解析

把常量池中的符号引用替换为直接引用,也可能延迟到实际使用时完成。

初始化

执行编译器生成的类初始化方法,内容来自静态字段赋值和静态初始化块,并按源码顺序组织。

常见主动初始化场景:

  • new 创建对象。
  • 读写非编译期常量的静态字段。
  • 调用静态方法。
  • 通过反射使用类。
  • 初始化子类前初始化父类。
  • JVM 启动时初始化主类。

访问父类声明的静态字段、创建某类型数组、引用已内联的编译期常量,不一定触发目标类初始化。

类加载器层次

加载器主要职责
Bootstrap ClassLoader加载 java.base 等核心模块,Java 层常显示为 null
Platform ClassLoader加载平台模块
Application ClassLoader加载应用类路径或模块路径中的类
自定义 ClassLoader插件隔离、热部署、加密类加载等

双亲委派

加载请求通常先委托父加载器,父加载器无法完成时,子加载器才自行尝试。

好处:

  • 避免核心类重复定义。
  • 防止应用伪造 java.lang.String 等核心类。
  • 让基础类型在不同加载器之间保持统一。

双亲委派是常见实现约定,不是不可改变的语法规则。Tomcat、OSGi、SPI 等场景会使用子优先或线程上下文类加载器。

loadClass 与 Class.forName

  • ClassLoader.loadClass() 默认只加载,不主动初始化。
  • Class.forName(name) 默认加载并初始化。
  • Class.forName(name, false, loader) 可以不初始化。

常见异常

  • ClassNotFoundException:显式加载时找不到类。
  • NoClassDefFoundError:运行时无法定义类,或类初始化曾失败。
  • ClassCastException:同名类由不同加载器定义也可能无法转换。
  • ExceptionInInitializerError:静态初始化逻辑抛出异常。

排查时应同时检查类名、JAR 版本、类路径、实际加载器和最初的初始化异常。

延伸阅读