Featured image of post 类加载机制

类加载机制

Java 类生命周期

java-class-life

类加载过程

类加载主要有三步:加载 - 链接 - 初始化

🥎 加载

加载是一个读取 Class 文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于调用的 java.lang.Class 类型的对象的过程。

loading

分为三步进行:

  • 获取定义类的二进制字节流(不限制从哪里获取,可以从文件、网络、即时生成)

  • 将字节流代表的静态存储结构,转换成方法区中运行时数据结构

  • 在堆中生成一个代表该类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

🥎 链接

⚾️ 验证

verify

  • 文件格式验证

    验证字节流是否符合 Class 文件格式的规范,能被当前版本的虚拟机处理。发生在加载阶段

  • 元数据、字节码验证

    对字节码进行语法、语义的分析,保证其符合 Java 虚拟机的规范,不会有危害虚拟机的行为。

  • 符号引用验证

    确保解析行为能够正常执行。发生在解析阶段

⚾️ 准备

为类中定义的变量(静态变量 static)分配内存并设置类变量零值的阶段。

prepare

  • static -> 赋零值
  • static final -> 赋定义的常量值

⚾️ 解析

解析是将常量池中的符号引用替换为直接引用。

  • 符号引用:描述引用对象的符号

  • 直接引用:指向目标实际地址的指针

🎾 解析部分是灵活的,可以在初始化环节后再进行,实现所谓的“后期绑定”。(方法调用直到运行时才会解析,因为无法在编译时确定方法调用所需的所有信息,所以方法定义和方法调用直到运行时才绑定。)

resolve

当一个类被编译成 Class 之后,假设这个类称为 A,并且在类 A 中引用了类 B

  • 在编译阶段,A 无法确定 B 是否被编译(现在 B 一定未加载),此时 A 无法知道 B 的实际地址,所有在 A.Class 中,会使用一个字符串代表 B,这个字符串被称为符号引用

  • 在运行阶段,A 发生了加载,在解析时,发现其中的 B 还未被加载,就会触发类 B 的加载,将 B 加载到虚拟机中。此时,A 中的符号引用会被替换为 B 中的实际地址(也就是直接引用)。

resolve

  • 静态解析

    B 是具体的实现类,解析的对象十分明确,即会进行静态解析。

  • 动态解析

    Java 通过后期绑定的方式实现多态,通过动态解析实现。

    如果类 B 是抽象类或者接口,有具体的实现类 CD,当前具体的实现方式并不明确,无法确定使用哪个具体实现。

    直到运行过程中发生了调用,虚拟机调用栈中会得到具体类的信息,再进行解析,就有明确的直接引用代替符号引用。

🥎 初始化

虚拟机真正开始执行类中编写的代码,完成一些主动的资源初始化动作。

执行的是类层面的初始化。 只有显式调用 new 才会执行构造函数的初始化。

init

  • 类变量(静态变量)的赋值 static
  • 静态代码块 static { }

如何判断无用类

  • 该类的所有实例对象全部被 GC,在堆中不存在该类的任何实例对象
  • 该类对应的 java.lang.Class 对象,在任何地方都没有被引用
  • 该类的类加载器已经被回收

满足以上三个条件,说明该类可以被回收,但是与对象不同,无用类不一定会被回收。

参考