深入理解虚拟机类加载机制

类加载的时机

类加载的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)这七个阶段。其中验证、准备、解析统称为连接(Linking),如下图所示。
这里写图片描述
其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,解析则不一定,在某些情况下可以再初始化之后进行,这是为了支持Java语言的运行时绑定(动态绑定)。

加载

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

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

对于类加载的其它过程,一个非数组类的加载过程是可控性最强的。可以使用系统提供的引导类记载器完成,也可以通过用户自定义的类加载器,开发人员可以通过定义自己的类加载器去控制字节流的获取方式(重写一个类加载器的loadClass()方法)。
对于非数组来说来说,数组类本身不通过类加载器创建,是由Java虚拟机直接创建的。但是数组类里的元素类型(Element Type)是靠类加载器创建的。

验证

验证的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段大致上会完成下面四个阶段的检验动作:文件格式的验证、元数据的验证、字节码验证、符号引用验证。

文件格式的验证:
该阶段的验证是保证输入的字节流能正确解析并存储于方法区内。
这个阶段的验证是基于二进制字节流进行的,只有通过这个阶段的验证,字节流才会进入内存的方法区中进行存储,所以后面的3个验证阶段全部是基于方法的存储结构进行的,不会再直接操作字节流。

  1. 是否以魔术0xCAFEBABE开头
  2. 主、次版本号是否在当前虚拟机处理范围之内
  3. 常量池的常量中是否有不被支持的常量类型
  4. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

元数据验证:
主要是对元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。

  1. 这个类是否有父类
  2. 这个类的父类是否继承了不允许被继承的类
  3. 如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法

字节码验证:
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

  1. 操作栈是一个int类型的数据,使用时却按long类型来加载入本地变量表中
  2. 保证跳转指令不会跳转到方法体以外的字节码指令上

符号引用验证
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将发生在解析阶段中(在下面将会介绍)。

  1. 符号引用中通过字符串描述的全限定名是否能找到对应的类
  2. 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中进行。
注:

  1. 这时候进行内存分配仅包括类变量(静态变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中
  2. 这里说的初始化指的是在数据类型的零值。例如int类型的零值是0,long的零值是0L

解析

所谓解析,就是虚拟机将常量池内的符号引用替换成直接引用的过程。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关。
直接引用:直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄,和虚拟机实现的内存布局有关。
解析主要分为以下四个阶段:

  1. 类或接口的解析
  2. 字段解析
  3. 类方法解析
  4. 接口方法解析

初始化

类初始化是类加载器过程的最后一步,到了初始化阶段,才真正开始执行类中定义的Java程序代码,主要对类变量进行初始化。
立即对类进行“初始化”的5种情况:

  1. 遇到new,getstatic,putstatic,invokestatic这4个字节码指令,如果类没有进行过初始化,需要先触发类的初始化
  2. 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,需要先触发类的初始化
  3. 当初始化一个类时,如果发现其父类还没有进行过初始化,先触发其父类的初始化
  4. 当虚拟机启动时,用户需要制定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
  5. 当使用jdk1.7,如果一个java.lang.invoke,MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则先触发类的初始化

注:所有引用类的方式都不会触发初始化。

结束生命周期

以下几种情况会使虚拟机结束生命周期: 1. 执行了System.exit()方法 2. 程序正常执行结束 3. 程序在执行过程中遇到了异常或错误而异常终止 4. 由于操作系统出现错误而导致Java虚拟机进程终止