上一篇文章了解了 Class 文件存储格式的具体细节,这些 Class 文件最终都需要加载到虚拟机中之后才能运行和使用,而虚拟机如何加载这些 Class 文件? Class 文件中的信息进入到虚拟机后会发生什么变化?这些都是本篇文章要讲解的内容.

与那些编译时需要连接的语言不同, 在Java中,类型的加载,链接和初始化的过程都是在程序运行时进行的, Java 的动态扩展特性就是依赖这个特点实现.

1.1 类加载的时机

类被加载到虚拟机内存到卸载内存,整个生命周期包括:加载, 验证,准备,解析,初始化,使用,和卸载7个阶段.
在初始化阶段,虚拟机规定有且只有下面5种场景会对类进行初始化

  • 遇到 new,getstatic,putstatic 或 invokestatic 这4条字节码指令时
  • 对类进行反射
  • 当初始化一个类时,其父类还被初始化,则先初始化其父类
  • 当虚拟机启动时,虚拟机会先初始化用户指定类的 main 方法
  • 当使用 JDK1.7的动态语言支持时,如果 MethodHandler 实例解析结果 REF_getStatic,REF_putStatic,REF_invokeStatic 的方法句柄,并且这个句柄对应的类没有初始化,则需先初始化.

不会对类进行初始化的情况:

  • 对于静态字段,只有直接定义这个字段的类才会初始化
  • 通过数组定义引用类,不会触发引用类的初始化
  • 常量在编译阶段会存入调用类的常量池,本质上没有引用到定义常量的类,因此不会触发定义常量类的初始化.

1.2 类加载的过程

类加载的过程包括:加载,验证,准备,解析和初始化这5个阶段.

1.2.1 加载

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

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

1.2.2 验证

验证是连接阶段的第一步,这一阶段目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全.

  1. 文件格式验证

    验证字节流是否符合 Class 文件格式规范,并且能被当前版本虚拟机处理.

  2. 元数据验证

    对字节码描述的信息进行语义分析,确保符合 Java 语言规范.如:这个类是否有父类,是否继承了不允许继承的类等.

  3. 字节码验证

    通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的.

  4. 符号引用验证

    这个阶段的校验发生在虚拟机将符号引用转化为直接引用时.通过字符串描述的全限定名是否能找到对应的类,等.

1.2.3 准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段.初始值”通常情况”下是数据类型的零值,如:

1
public static int value = 123;

那变量value 在准备阶段后的初始值是0而不是123.

1.2.4 解析

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

  • 符号引用:以一组符号来描述所引用的目标.
  • 直接引用:可以是指向目标的指针,相对偏移量或者一个能间接定位目标的句柄

1.2.5 初始化

初始化阶段是执行类构造器()方法的过程

  • () 方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的局域合并产生.
  • 虚拟机会保证在子()方法执行之前会先执行父类中的()方法

1.3 类加载器

虚拟机把类加载阶段中”通过一个类的全限定名获取到这个类的字节流”方法虚拟机外部去实现,以便让程序自己决定如何去获取所需要的类.这个动作的代码模板称为”类加载器”

1.3.1 类与类加载器

比较这两个类是否相等,只有这两个类使用同一个类加载器加载才有意义,否则,即使这两个类来源于同一个类, 被同一个虚拟机加载,只要加载类的加载器不同,那这两个类就必不相等.

1.3.2 双亲委派模型

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,这之间的父子关系一般不使用继承,而都使用组合关系来复用父加载器的代码.
当一个类加载器收到了类加载请求,首先不会自己加载这个类,而是把这个请求委派给父类加载器去完成,每一层都是如此,因此所有的加载请求都会传送到顶层启动类加载器中,只有父类反馈自己无法完成这个请求时,子类才会尝试自己加载.