Java 的类加载器(ClassLoader)

类加载器是 JVM 中一个非常重要的子系统,它负责将 Class 文件(包含类的字节码)加载到 JVM 的运行时数据区(主要是方法区)中,并在堆内存中创建对应的 java.lang.Class 对象,作为程序中访问这些类的入口。

类加载器的作用:

  • 加载: 从不同的来源(如本地文件系统、JAR 包、网络等)读取 Class 文件的二进制字节流。
  • 链接: 执行连接阶段的验证、准备和解析(解析是可选的)。
  • 初始化: 执行类的初始化方法 <clinit>()

类加载器的层次结构和双亲委派模型:

为了更好地管理类加载过程,并保证 Java 核心库的稳定性和安全性,JVM 采用了双亲委派模型(Parent-First Delegation Model)。在这个模型中,类加载器之间存在一种层次关系,通常由以下几个主要的类加载器组成:

  1. 启动类加载器 (Bootstrap ClassLoader):

    • 这是 JVM 最底层的类加载器,由 C++ 实现,是 JVM 自身的一部分。
    • 它负责加载 JVM 启动时需要的核心类库,例如 java.lang.*java.util.* 等,这些类库通常位于 <JAVA_HOME>/lib 目录下。
    • 启动类加载器没有父类加载器。
  2. 扩展类加载器 (Extension ClassLoader):

    • 由 Java 语言实现(sun.misc.Launcher$ExtClassLoader)。
    • 它是启动类加载器的子类加载器。
    • 它负责加载 <JAVA_HOME>/lib/ext 目录下的,或者被 java.ext.dirs 系统变量所指定的路径下的所有 JAR 包中的类库。
    • 它的父类加载器是启动类加载器。
  3. 应用程序类加载器 (Application ClassLoader) 或系统类加载器 (System ClassLoader):

    • 由 Java 语言实现(sun.misc.Launcher$AppClassLoader)。
    • 它是扩展类加载器的子类加载器。
    • 它负责加载用户在命令行中指定的 classpath 下的类库,或者是由 java.class.path 系统变量所指定的路径下的类库。
    • 它是我们平时开发中最常用的类加载器,也是默认情况下加载应用程序中类的加载器。
    • 它的父类加载器是扩展类加载器。

除了以上三个主要的类加载器,用户还可以根据自己的需求创建自定义的类加载器 (User-Defined ClassLoader),通过继承 java.lang.ClassLoader 类并重写其 findClass() 方法来实现特定的类加载逻辑。

双亲委派模型的工作流程:

当一个类加载器收到加载一个类的请求时,它不会立即自己去加载,而是将这个请求委派给它的父类加载器,直到委派给最顶层的启动类加载器。只有当父类加载器在它的搜索范围内找不到所需的类时(即无法完成加载),子类加载器才会尝试自己去加载。

双亲委派模型的好处:

  • 安全性: 避免用户自定义的恶意类替换 JVM 核心类库中的类。例如,即使你创建了一个名为 java.lang.String 的类,由于类加载请求首先会委派给启动类加载器,而启动类加载器会优先加载它自己负责的 java.lang.String,因此你的自定义类不会被加载。
  • 避免类的重复加载: 确保一个类只会被加载一次,不同的类加载器加载同一个类最终会委托给同一个父加载器,如果父加载器已经加载过该类,则会直接返回已加载的 Class 对象。这有助于维护类的一致性。

破坏双亲委派模型的情况:

虽然双亲委派模型是 JVM 推荐的类加载机制,但在某些特殊情况下,为了满足特定的需求,可能会出现破坏双亲委派模型的情况:

  • 线程上下文类加载器 (Thread Context ClassLoader): JNDI、JDBC 等 SPI (Service Provider Interface) 机制中会用到线程上下文类加载器。SPI 的接口通常由引导类加载器加载,但接口的实现类可能由应用程序类加载器加载。为了让引导类加载器能够加载到这些实现类,需要通过线程上下文类加载器向上委托给应用程序类加载器。
  • 热部署和模块化: 在某些需要动态更新和替换模块的场景下,例如 OSGi 框架,可能会创建自定义的类加载器来加载和卸载模块,这可能会打破严格的双亲委派模型。

自定义类加载器:

用户可以通过继承 java.lang.ClassLoader 类并重写其 findClass(String name) 方法来实现自定义的类加载逻辑。在 findClass() 方法中,你需要根据类的全限定名,从特定的来源(例如加密过的 Class 文件、网络)读取类的字节码,并调用 defineClass(String name, byte[] b, int off, int len) 方法将字节码转换为 Class 对象。

使用自定义类加载器的场景:

  • 加载特定来源的类文件: 例如,从网络、数据库或加密过的文件中加载类。
  • 实现类的隔离: 不同的类加载器可以加载相同全限定名的类,从而实现类之间的隔离,避免命名冲突。
  • 实现热部署: 通过创建新的类加载器加载新的类版本,并在需要时卸载旧的类加载器和类。
  • 扩展 JVM 的类加载机制。

总结:

类加载器是 JVM 核心组件之一,负责将 Class 文件加载到 JVM 中。双亲委派模型是一种重要的类加载机制,它保证了 Java 核心库的安全性和类的唯一性。用户也可以通过自定义类加载器来扩展 JVM 的类加载能力,以满足特定的应用需求。理解类加载器的工作原理对于深入理解 Java 的运行时行为至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值