学习java有一段时间了,很好奇java的虚拟机是怎么加载class的,正好今天有空,就查阅了一些资料,总结了一下,顺便记录下来,有不对之处请网上大神斧正。

一、流程图

  • 类加载流程图
1
2
3
graph LR
加载-->链接
链接-->初始化
  • 链接过程又分为三步
1
2
3
graph LR
验证-->准备
准备-->解析

二、加载

此阶段是类装载类的第一个阶段,负责通过各种方式获取类的二进制流,转为方法区数据结构,并在JAVA堆中生成对应的java.lang.Class对象

三、链接

3.1 验证

验证的目的是保证Class流的格式是正确的。一个类通过了字节码的验证并不代表该类没有问题,反之如果没有通过字节码的验证,那么该类一定存在问题。

  • 文件格式验证

    是否以0xCAFEBABE开头,版本号是否合理

  • 元数据验证

    是否有父类,是否继承了final类,非抽象类是否实现了所有的抽象方法(编译环节就通不过,不知道为什么类加载过程中还要验证)。

  • 字节码验证

    该验证最复杂,主要运行检查,栈数据类型和操作码数据参数是否吻合,跳转指令指定到合理的位置

  • 符号引用验证

    常量池中描述类是否存在,访问的方法或字段是否存在且有足够的权限(NoSuchMethodError, NoSuchFieldError,IllegalAccessError错误就发生在该阶段)

    3.2 准备
    准备阶段主要是分配内存,并在方法区中为类设置初始值。
    例如:
    1
    2
    3
    4
    public static int a=1;
    public static final int b=1;
    //1、a=1会在准备阶段先设置为0,在初始化的<clinit>中才会被设置为1
    //2、b=1(static final)会在准备阶段直接设置为1,直接赋准确的值。
    3.3 解析
    本阶段是将符号引用替换为直接引用。字符串引用对象不一定被加载,指针或者地址偏移量引用对象一定在内存

四、初始化

  • 执行类构造器:执行static变量赋值语句,static{}语句
  • 子类的调用前保证父类的被调用
  • 是线程安全的

类的初始化顺序图:

1
2
3
4
5
6
7
父类静态成员变量-->父类静态初始化块
父类静态初始化块-->子类静态成员变量
子类静态成员变量-->子类静态初始化块
子类静态初始化块-->父类普通成员变量
父类普通成员变量-->父类构造方法
父类构造方法-->子类普通成员变量
子类普通成员变量-->子类构造方法