Java类加载过程、加载器详解(委派模型)

一、类的加载过程

为了让程序能够正确执行、创建对象、访问成员、继承实现等,需要将类加载到虚拟机内!!!

1、类的生命周期

从被加载到虚拟机内存中开始到卸载出内存为止,类的整个生命周期可以简单概括为 7 个阶段::加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、准备和解析这三个阶段可以统称为连接(Linking)。如图:
在这里插入图片描述

2、当类被加载到Java虚拟机内存中时,会经历以下步骤:

  1. 加载(Loading):
    加载是指通过类加载器将类的.class文件加载到Java虚拟机内存中。在加载阶段,虚拟机会读取类的二进制数据,并创建一个代表该类的Class对象。加载阶段的工作包括验证类的格式、解析符号引用等。
  2. 链接(Linking):
    链接阶段分为三个步骤:验证、准备和解析。
    ①验证(Verification):确保加载的类符合Java语言规范和虚拟机的约束条件,包括文件格式验证元数据验证字节码验证符号引用验证等。
    ②准备(Preparation):为类的静态变量分配内存,并设置默认初始值(零值)。
    ③解析(Resolution):将类、接口、字段和方法的符号引用解析为直接引用。
  3. 初始化(Initialization):
    初始化阶段是类加载的最后一步,它会执行类构造器(方法)的代码,对类的静态变量进行初始化赋值等操作。在初始化阶段,虚拟机会按照严格规定的顺序来初始化类和接口,确保线程安全性。
    一旦类完成加载、链接和初始化,就可以在Java虚拟机中被使用了。需要注意的是,类加载过程是懒加载的,即在第一次使用类时才会进行加载、链接和初始化操作。

3、类卸载

卸载类即该类的 Class 对象被 GC。卸载类需要满足 3 个要求:
①该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
②加载该类的 ClassLoader 已经被回收。
③该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
在 JVM 生命周期内,由 jvm 自带的类加载器加载的类是不会被卸载的。但是由我们自定义的类加载器加载的类是可能被卸载的。

二、类的加载器

1、类加载器的作用

负责将类的字节码文件加载到Java虚拟机内存中。它根据类的全限定名(Fully Qualified Name)从文件系统、网络或其他来源加载类的字节码,并创建表示该类的Class对象。
①类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
②每个 Java 类都有一个引用指向加载它的 ClassLoader。
③数组类不是通过 ClassLoader 创建的(因为数组类没有对应的二进制字节流),而是由 JVM 直接生成的。

2、类加载器加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

3、JVM 中内置了三个重要的 ClassLoader:

①BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jar、resources.jar、charsets.jar等 jar 包和类)以及被 -Xbootclasspath参数指定的路径下的所有类。
②ExtensionClassLoader(扩展类加载器):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
③AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

4、双亲委派模型

用来判断具体用哪个加载器来加载类,双亲委派模型的执行流程:

  1. 收到加载请求:当一个类加载器收到加载类的请求时,它首先会检查是否已经加载过这个类。如果该类还没有被加载过,当前类加载器会将加载请求委派给它的父类加载器去尝试加载。
  2. 递归委派:父类加载器收到请求后,会重复步骤1,即检查是否已经加载过这个类,如果没有,则继续将加载请求委派给它的父类加载器。直至达到启动类加载器。启动类加载器通常负责加载Java核心类库,如果启动类加载器也无法加载该类,则说明该类尚未被加载过。
  3. 尝试加载类:当所有父类加载器都无法加载该类时,当前类加载器才会尝试自己加载。它会根据自己的加载规则和策略,从指定的位置加载类的字节码文件。
  4. 加载类:如果成功找到并加载了类的字节码文件,则会创建表示该类的Class对象,并将其返回给请求方。
  5. 如果子类加载器也无法加载这个类,那么它会抛出一个 ClassNotFoundException 异常。

双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),避免类冲突和版本冲突的问题,也保证了 Java 的核心 API 不被篡改。

5、逆向委派

有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。

假设有一个自定义类加载器 A,它要加载的类依赖于特定的库,而这个特定的库又是由另一个自定义类加载器 B 加载的。在标准的双亲委派模型中,类加载器 A 需要加载的类会先委派给其父类加载器去加载,但是父类加载器并没有加载这个特定的库,因此无法加载类。

  • 在这种情况下,类加载器 A 就需要直接尝试加载所需的类,而不是按照双亲委派模型委派给父类加载器。它可能会通过一些特定的方式来获取类加载器 B,并使用类加载器 B 加载所需的特定库,然后再加载自己需要的类。
  • 比如利用线程上下文类加载器,将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的。让高层的类加载器借助子类加载器来加载业务类,破坏Java 的类加载委托机制,让应用逆向使用类加载器,这种情况称为“逆向委派”。
  • 35
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值