面试题:你了解 Java 的类加载器吗?

类加载器是 Java 虚拟机(JVM)的一个核心组成部分,负责在程序运行时动态地将 .class 文件(字节码)加载到 JVM 中,并将其转换为 Java 的 java.lang.Class 对象。

类加载器使得 Java 能够在运行时动态加载类,支持了诸如热部署、插件化、模块化等高级特性。


类加载器的职责

  1. 加载(Loading):通过类的全限定名(Fully Qualified Name)获取其对应的字节码,并将其读入内存,创建一个 Class 对象。
  2. 链接(Linking)
    • 验证(Verification):确保加载的字节码是合法且安全的,符合 JVM 规范。
    • 准备(Preparation):为类的静态变量分配内存,并设置默认初始值(如 0falsenull)。
    • 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用(可选在初始化时进行)。
  3. 初始化(Initialization):执行类的静态初始化块和静态变量的赋值操作,完成类的最终准备。

Java 中的类加载器层次结构

Java 的类加载器采用 双亲委派模型(Parent Delegation Model),形成一个层次结构:

  1. 启动类加载器(Bootstrap ClassLoader)
    • 由 C/C++ 实现,是 JVM 的一部分,不是 Java 对象。
    • 负责加载 Java 核心类库,位于 JAVA_HOME/jre/lib 目录下(如 rt.jar),例如 java.lang.*java.util.* 等。
    • 无法被 Java 程序直接引用。
  2. 扩展类加载器(Extension ClassLoader / Platform ClassLoader)
    • 由 Java 实现,通常是 sun.misc.Launcher$ExtClassLoader(旧版本)或 jdk.internal.loader.ClassLoaders$PlatformClassLoader(JDK 9+ 模块化后)。
    • 负责加载 Java 的扩展库,位于 JAVA_HOME/jre/lib/ext 目录下,或由系统属性 java.ext.dirs 指定的路径。
    • 它的父类加载器是 Bootstrap ClassLoader。
  3. 应用程序类加载器(Application ClassLoader / System ClassLoader)
    • 由 Java 实现,通常是 sun.misc.Launcher$AppClassLoader
    • 负责加载用户类路径(Classpath)上指定的类,即我们自己编写的代码和第三方 jar 包。
    • 它的父类加载器是 Extension/Platform ClassLoader。
    • 这是默认的类加载器,ClassLoader.getSystemClassLoader() 返回的就是它。
  4. 自定义类加载器(Custom ClassLoader)
    • 开发者可以通过继承 java.lang.ClassLoader 抽象类来创建自己的类加载器。
    • 用于实现特殊需求,如热部署、隔离加载、加密类加载、模块化(OSGi)、远程类加载等。

双亲委派模型的工作机制

当一个类加载器收到类加载请求时,它不会立即自己去加载,而是将这个请求委派给其父类加载器去完成。只有当父类加载器无法完成加载(返回 ClassNotFoundException)时,子类加载器才会尝试自己去加载。

工作流程

  1. 应用程序类加载器收到请求。
  2. 委托给扩展类加载器。
  3. 扩展类加载器再委托给启动类加载器。
  4. 启动类加载器尝试加载,如果找不到,则返回失败。
  5. 扩展类加载器尝试加载,如果也找不到,返回失败。
  6. 最终由应用程序类加载器尝试加载。

双亲委派模型的优点

  1. 避免重复加载:确保一个类只会被加载一次。
  2. 安全性:防止核心 API 被恶意篡改。例如,即使你写了一个 java.lang.String 类,由于双亲委派,Bootstrap ClassLoader 会优先加载 JDK 中的 String,保证了核心类库的稳定性和安全性。

破坏双亲委派的场景

虽然双亲委派是推荐模型,但在某些特殊场景下会被“破坏”:

  1. SPI(Service Provider Interface):如 JDBC、JNDI 等。父类加载器(Bootstrap)需要加载由用户实现的接口实现类(在应用 ClassPath 下),但父类加载器无法访问应用类路径。此时通过 Thread.currentThread().getContextClassLoader() 获取应用类加载器来完成加载。
  2. OSGi 模块化框架:为了实现模块间的类隔离和动态加载,OSGi 实现了自己复杂的类加载机制,打破了传统的双亲委派。
  3. 热部署/热更新:为了实现模块的独立加载和卸载,需要自定义类加载器并绕过双亲委派。

总结

类加载器是 Java 实现动态性、灵活性和安全性的基石。

理解类加载器的层次结构、双亲委派模型及其工作原理,对于深入掌握 Java 运行机制、排查类加载问题(如 ClassNotFoundExceptionNoClassDefFoundErrorClassCastException 等)以及开发高级框架(如 Spring、OSGi、应用服务器)都至关重要。

THE END
喜欢就支持一下吧
点赞6 分享