虚拟机类加载机制

330 阅读4分钟

内容摘抄自《深入理解Java虚拟机》

类的生命周期

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括七个阶段,如下图:

类的生命周期

类的加载过程

1、加载

在加载阶段,虚拟机需要完成以下3件事情:

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

2、验证

验证是虚拟机对自身保护的一项重要工作。从整体上来看,验证阶段分为以下4各阶段的检验动作:

  1. 文件格式验证
    验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。例如是否以魔数0xCAFEBABE开头;版本号是否在当前虚拟机的处理范围之内。
  2. 元数据验证
    对字节码描述的信息进行语义分析。例如这个类是否有父类,这个类的父类是否为不允许被继承的类等。
  3. 字节码验证
    字节码验证是整个验证过程最复杂的一个阶段。主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。例如保证跳转指令不会跳转到方法体以外的字节码指令上。
  4. 符号引用验证
    这一阶段发生在虚拟机将符号引用转化为直接引用的时候,即在连接的第三阶段——解析阶段中发生。

对虚拟机的类加载机制来说,验证阶段是一个非常重要的、但不一定必要(因为对程序运行期没有影响)的阶段。

3、准备

准备阶段是正式为类变量分配内存并设置初始值的阶段。

首先,进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将在对象实例化时随着对象一起分配在java堆中。其次,这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

public static int value = 123;

那变量value在准备阶段过后的初始值为0而不是123,而把value赋值为123的putstatic指令是程序编译后,存放于类构造器<clint>()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。

4、解析

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

  • 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
  • 直接引用:可以是指向目标的指针、相对偏移量或一个能间接定位到目标的句柄。

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

5、初始化

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义的加载器参与之外,其余动作完全有虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码。
初始化阶段是执行类构造器<clint>()方法的过程。

  • <clint>()方法是由编译器自动收集类中的所有类变量的赋值动作静态语句块static{}块)中的语句合并产生的。
  • <clint>()方法与类的构造函数(或者说实例构造器<int>()方法)不同,它不需要显示地调用父类构造器,虚拟机保证在子类的<clint>()方法执行之前,父类的<clint>()方法已经执行完毕。
  • 虚拟机会保证一个类的<clint>()方法在多线程环境中被正确地加锁、同步。