jvm的类加载机制

129 阅读7分钟

序言:

jvm的类加载机制一直是面试的热点题型,与其去背八股文,不如主动出击,去深入了解jvm的类加载机制。要了解jvm的的类加载机制,首先需要了解jvm为什么能够一次编译,到处运行。简单来说就是java编译的时候把java程序编译成字节码。在程序运行时,虚拟机将字节码翻译成特定平台下的机器码并运行。这样的话只要在不同机器上安装相应的jvm就可以运行java程序编译产生的字节码。

类加载机制的三个阶段

类的加载是Java虚拟机(JVM)将类的字节码文件加载到内存中并创建对应的Class对象的过程。jvm的类加载机制包括三个阶段,加载、连接和初始化。以下详细介绍一下这三个阶段。

  1. 加载

    ​ 加载是类加载机制的第一个阶段,在这个阶段,类加载器负责查找并加载类的字节码文件。它将类的字节码文件从文件系统、网络或其他来源加载到内存中,并将字节码文件所代表的静态存储结构转化为方法区的运行时数据结构,最后在java堆中生成一个代表这个类的对象作为方法区中这些数据的访问人口。加载阶段完成后,JVM就知道了这个类的存在,但尚未进行具体的初始化工作。

  2. 连接

    连接阶段包括验证、准备和解析三个步骤,除部分验证阶段的内容是夹杂在加载阶段进行的,这些步骤在加载阶段之后依次进行。

    • 验证(Verification):在验证阶段,JVM对类的字节码进行验证,以确保其符合JVM规范和安全性要求。验证过程会检查字节码的结构和语义,以防止潜在的安全风险和错误。验证阶段大致可分为四个阶段

      1. 文件格式验证(File Format Verification):文件格式验证是验证阶段的一部分,用于确保类的字节码文件的格式正确并且与 JVM 规范相符。在这个阶段,JVM 会检查字节码的结构,确保它遵循了正确的格式,包括标识符、常量池、字段表、方法表、属性表等的正确性。这有助于防止恶意构造的字节码文件对 JVM 的影响。
      2. 元数据验证(Metadata Verification):元数据验证是确保类的元数据信息(如类、方法、字段的签名、访问权限等)与实际字节码内容相符的过程。元数据验证会检查类的继承关系、方法参数和返回值的类型匹配、字段的数据类型等。这个阶段有助于保证类的元数据是合法的,不会导致类型转换错误或方法调用错误。
      3. 字节码验证(Bytecode Verification):这是验证阶段的主要部分,用于确保类的字节码符合 JVM 规范。字节码验证会检查字节码的结构、操作码的合法性、操作数的类型匹配等,以防止潜在的错误或安全风险。这个过程有助于防止恶意代码或不合法代码对 JVM 的破坏。
      4. 符号引用验证(Symbolic Reference Verification):在解析阶段将符号引用解析为直接引用之前,需要进行符号引用验证。这确保了引用的目标(如类、方法、字段)确实存在,并且权限是正确的。如果符号引用验证失败,JVM 将抛出异常。
      5. 存储类的验证(Structural Verification):在类层次结构中,父类和子类之间的关系必须是正确的。存储类的验证会检查继承关系、方法的重写规则等,以确保类的继承结构和方法调用是合法的
    • 准备(Preparation):在准备阶段,JVM为类的静态变量分配内存,并设置默认初始值。这些静态变量会被初始化为默认值(例如,数值类型为0,引用类型为null)。

    • 解析(Resolution):在解析阶段,JVM将符号引用转换为直接引用。符号引用是一种在字节码中表示被引用类、方法、字段的标识,而直接引用是可以直接指向内存中的实际对象、方法、字段。

  3. 初始化

    初始化阶段是 Java 虚拟机类加载过程的最后一个阶段,也是类加载的最重要的阶段之一,同时初始化阶段是类加载过程中最耗时的部分之一,因此应谨慎地编写和使用初始化代码,避免不必要的性能开销。在初始化阶段,JVM 执行类的初始化代码,包括为静态变量赋值和执行静态初始化块(static initializer blocks)中的代码。这个阶段的目标是确保在首次使用类时,类的状态是正确的,静态资源被正确地初始化。

    初始化阶段的主要特点和行为包括:

    1. 静态变量赋值:JVM 会为类的静态变量分配内存并赋予初始值。这些初始值可以是在变量声明时直接赋予的值,或者是默认的零值。静态变量赋值的顺序遵循程序的书写顺序。
    2. 静态初始化块:如果类中包含静态初始化块(static initializer blocks),JVM 会在初始化阶段执行这些块中的代码。静态初始化块用于执行复杂的初始化逻辑,可以初始化静态变量或执行其他操作。
    3. 父类的初始化:在初始化一个类之前,它的父类会先被初始化。这确保了类的继承层次结构中的父类在子类之前正确初始化。
    4. 单例模式的实现:在初始化阶段,可以实现单例模式的初始化逻辑。通过将构造方法私有化并提供一个静态方法获取单例实例,可以确保只有一个实例被创建。
    5. 静态资源的初始化:静态资源如数据库连接、配置文件等可以在初始化阶段被初始化,确保应用程序的静态资源处于正确状态。

    初始化阶段是在类被加载、连接之后执行的,而且是在类的首次实际使用(如创建实例、调用静态方法等)时触发的。如果一个类没有被使用,那么它的初始化阶段就不会被执行。

类加载器:

类加载器(Class Loader)是 Java 虚拟机(JVM)的重要组成部分,负责将类的字节码文件加载到内存中,并创建对应的 Class 对象。类加载器在 Java 程序中起着关键的作用,它允许在运行时动态加载类,提供了灵活性和扩展性类加载器有以下几种。

  1. 启动类加载器:是jvm的一部分,负责加载核心库类,他是其他类加载器的根源
  2. 扩展类加载器:负责加载JRE目录中的类。
  3. 应用程序类加载器:负责加载应用程序类路径中指定的类

应用程序就是由以上三种类加载器互相配合进行加载的,除此之外还可以自定义类加载器

双亲委派模型

双亲委派模型是一种用于保证类加载的唯一性和安全性的机制,它通过类加载器之间的委派关系来避免重复加载并保护核心类库的完整性。具体实现步骤是这样的:

  1. 当一个类加载器收到加载请求时,首先会检查是否加载过这个类,如果已经加载就直接返回已加载到类而不会重复加载
  2. 如果没有加载这个类,子加载器就会委派父加载器来加载。父类加载器也会进行相同步骤,一直递归委派加载请求,直到顶层的启动类加载器。
  3. 如果父类加载器无法加载,子类就会尝试查找并加载类的字节码
  4. 子类加载器查找到类的字节码并成功加载,就会返回对应的class对象供程序使用。

​ 这样的话,核心的库类,比如object类通常会由启动类加载器加载,而用户自定义的加载器就会应用程序类加载器加载。