Java 类加载的生命周期:

  • Loading,加载字节流
  • Linking,链接
    • Verification,校验合法性
    • Preparation,准备阶段,分配内存,初始值
    • Resolution,解析阶段,常量池替换引用,变成直接引用
  • Initialization 初始化,调用类构造器
  • Using 使用阶段
  • Unloading,卸载

其中第二步到第四步,验证,准备,解析统一称为 Linking 过程。

在前5个阶段,加载、验证、准备和初始化四个阶段顺序是确定的,但是解析阶段不一定。某些情况下会在初始化阶段之后开始,为了支持 Java 语言的运行时绑定(动态绑定)。

这几个阶段是按顺序开始,而不是按顺序完成的,这些阶段通常是交叉进行的,通常会在一个阶段执行的过程中调用或激活另一个阶段。

Loading

加载阶段是查找并加载类的二进制数据,在加载阶段,JVM 需要完成:

  • 通过类的全限定名获取二进制字节流
  • 将字节流的静态存储结构转化为方法区的运行时数据结构
  • 内存中生成一个代表类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

加载阶段是可控最强的阶段,开发人员可以使用系统的类加载器,或自定义类加载器完成加载。

加载完成,虚拟机外部的二进制字节流就按照虚拟机的格式存储在方法区了,而且在堆内存中也有一个 java.lang.Class 类对象。

获取字节流的方式

  • zip 包,JAR,WAR,EAR
  • 网络,Applet
  • 运行时,动态代理
  • 其他文件, JSP
  • 数据库等等

Verification

验证阶段会确保 Class 字节流符合虚拟机要求。

并且不会堆虚拟机产生安全问题。

验证阶段大致分成4个步骤:

  • 文件格式验证:验证字节流是否符合 Class 文件格式规范,比如是否以 0xCAFEBABE 开头、主次版本号是否在当前虚拟机的处理范围内
  • 元数据验证:对字节码描述的信息进行语义分析保证描述的信息符合 Java 语言规范,比如这个类是否有父类
  • 字节码验证:确定语义是否合法符合逻辑
  • 符号引用验证:确保解析动作能正确执行

文件格式

  • Magic Number, 0xCAFEBABE
  • Major, Minor Version 主次版本号
  • 常量池中常量是否有不被支持的类型
  • 索引值
  • CONSTANT_Utf8_info 常量
  • Class 文件

元数据校验

  • 父类,java.lang.Object
  • 父类是否继承了不允许修改的类,final
  • 如果不是抽象类,是不是实现了所有要求的方法
  • 类字段、方法是否有矛盾

字节码验证

  • 数据栈的类型和指令代码相吻合
  • 跳转指令不会跳转到方法体外
  • 方法体的类型转换

符号引用验证

  • 符号引用中通过字符串能否找到类
  • 指定类中符合方法的字段描述符
  • 访问属性

准备

准备阶段为类变量分配内存,设置初始值。

  • 内存在方法区分配被 static 修饰的变量,不包括实例变量,实例变量会在对象实例化时随对象一块分配在 Java 堆中

假如有类变量定义 public static int value = 1

那么变量 value 在准备阶段初始值是 0,而不是 1,把 value 赋值为 1 的 public statci 指令是在程序编译之后,存放Zeal类构造器方法中,把 value 赋值为 3 的动作在初始化阶段才会执行。

但是如果有定义 private static final int value = 3,也就是定义了 Constant 变量,同时被 final 和 static 修饰,那么在准备阶段变量 value 就会被初始化为指定的值。

解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

解析的动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用。

类或接口的解析

初始化

初始化为类的静态变量赋予初始值,JVM 负责堆类进行初始化,对类变量进行初始化。

Java 中堆类变量进行初始值设定有两种方式:

  • 声明类变量是指定初始值
  • 使用静态代码块为类变量指定初始值

JVM 初始化步骤:

  • 假如类没有被 Loading 和 Linking,程序会先 Loading 和 Linking 该类
  • 假如该类的直接父类没有初始化,则先初始化其直接父类
  • 假如类中有初始化语句,则系统依次执行这些初始化语句

类初始化时机:只有当对类的主动使用时才会导致类初始化,包括如下:

  • 类创建实例,new 方法
  • 访问某个类或接口的 静态变量,或对该静态变量赋值
  • 调用类的静态方法
  • 反射(比如 Class.forName("com.einverne.Test");
  • 初始化类的子类,父类也会被初始化
  • Java 虚拟机启动时被标明为启动类的类,直接使用 java.exe 命令来运行某个主类

结束生命周期

在如下几种情况下,Java 虚拟机将结束生命周期:

  • 执行 System.exit() 方法
  • 程序正常执行结束
  • 程序在执行过程中遇到了异常或错误而异常终止
  • 操作系统出现错误而导致 JVM 进程终止

Java 类加载器的类别

Java 如何实现自定义类加载器