JVM类加载思惟导图

用一张思惟导图尽量囊括一下JVM的类加载过程的全流程。java

本文参考自来自周志明《深刻理解Java虚拟机(第2版)》,拓展内容建议读者能够阅读下这本书。

class loading

文字版以下:数组

加载 Loading

过程

  • 经过类的全限定名来获取定义此类的二进制字节流数据结构

    • 非数组类的加载,由类加载器加载,能够是启动类加载器,也能够是用户自定义的类加载器
    • 数组类的加载,不禁类加载器建立,而是由JVM直接在内部建立多线程

      • 组件类型(数组降一维后的类型)是引用类型,递归调用加载过程直到降到一维类型后经过类加载器加载,数组类型最终标识为此类加载器所加载,数组类可见性和组件类型保持一致
      • 组件类型不是引用类型而是原始类型,则该数组类型的类加载器将标识为启动类加载器,数组类型可见性为public
  • 将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构
  • 在内存中(HotSpot为方法区)生成一个表明了这个类的java.lang.Class对象,做为方法区这个类的各类数据的访问入口

类加载器

  • 启动类加载器 Bootstrap ClassLoader,加载<JAVA_HOME>/lib中的类
  • 拓展类加载器 Extension ClassLoader,加载<JAVA_HOME>/lib/ext中的类
  • 应用程序类加载器 Application ClassLoader,加载用户类路径上的ClassPath中的类
  • 自定义类加载器 User ClassLoader

链接 Linking

验证 Verification

  • 文件格式验证:字节流是否符合Class文件格式规范布局

    • 是否以magic开头
    • 主次版本号是否在虚拟机处理范围内
    • 常量池中的常量是否有不支持的类型
    • 指向的常量索引值是否有指向不存在常量或不符合类型常量的状况
    • CONSTANT_Utf8_info的常量是否符合utf8编码规范
    • Class文件各个部分及文件自己是否有被删除或附加的其余信息
  • 元数据验证:字节码描述的信息进行语义分析编码

    • 是否有父类
    • 父类是否继承了不容许被继承的类(final的)
    • 若是不是抽象类是否实现了其父类或接口之中要求实现的类
    • 类中字段、方法是否与父类产生矛盾spa

      • 覆盖了父类的final字段
      • 不符合规范的方法重载
方法参数类型一致返回值类型不一致
  • 字节码验证:经过数据流和控制流分析程序语义的合法性,即类的方法体的校验分析线程

    • 保证时刻操做数栈与指令代码序列能配合工做
    • 保证跳转指令不会跳转到方法体之外的字节码指令上
    • 保证方法体的类型转换是有效的
  • 符号引用验证:类的常量池中各类符号引用的信息进行匹配性校验指针

    • 符号引用中经过字符串描述的全限定名是否能找到对应类
    • 指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
    • 符号引用中的类、字段和方法的访问性是否可被当前类所访问

链接 Linking

验证 Verification

  • 文件格式验证:字节流是否符合Class文件格式规范对象

    • 是否以magic开头
    • 主次版本号是否在虚拟机处理范围内
    • 常量池中的常量是否有不支持的类型
    • 指向的常量索引值是否有指向不存在常量或不符合类型常量的状况
    • CONSTANT_Utf8_info的常量是否符合utf8编码规范
    • Class文件各个部分及文件自己是否有被删除或附加的其余信息
  • 元数据验证:字节码描述的信息进行语义分析

    • 是否有父类
    • 父类是否继承了不容许被继承的类(final的)
    • 若是不是抽象类是否实现了其父类或接口之中要求实现的类
    • 类中字段、方法是否与父类产生矛盾

      • 覆盖了父类的final字段
      • 不符合规范的方法重载
方法参数类型一致返回值类型不一致
  • 字节码验证:经过数据流和控制流分析程序语义的合法性,即类的方法体的校验分析

    • 保证时刻操做数栈与指令代码序列能配合工做
    • 保证跳转指令不会跳转到方法体之外的字节码指令上
    • 保证方法体的类型转换是有效的
  • 符号引用验证:类的常量池中各类符号引用的信息进行匹配性校验

    • 符号引用中经过字符串描述的全限定名是否能找到对应类
    • 指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
    • 符号引用中的类、字段和方法的访问性是否可被当前类所访问

准备 Preparation

  • 类变量分配空间
  • 类变量分配零值
  • 类常量分配初始值:由类字段的ConstantValue属性进行赋值

解析 Resolution

  • 实际上就是把常量池中的符号引用替换为直接引用的过程

    • 符号引用

      • 在常量池中即非字面量的类型

        • CONSTANT_Class_info
        • CONSTANT_Fieldref_info
        • CONSTANT_Methodref_info
        • CONSTANT_InterfaceMethodref_info
      • 特征

        • 与虚拟机实现的内存布局无关
        • 引用的目标并不必定已经加载到内存中
        • 由虚拟机Class文件格式规范,所以不一样虚拟机可以接受的符号引用格式是肯定的
    • 直接引用

      • 表达形式

        • 直接指向目标的虚拟机内存中的指针
        • 相对偏移量
        • 可以定位到目标的句柄
      • 特征

        • 与虚拟机的内存布局直接相关
        • 引用的目标必须已经存在于内存中
        • 同一符号引用在不一样虚拟机中的直接引用通常不一样,由虚拟机本身制定格式
  • 符号引用解析

    • 类或接口的解析

      • 对CONSTANT_Class_info符号引用的解析

        • 对全限定名的解析
      • 虚拟机加载类D中的类符号引用N为一个类或接口C的直接引用

        • C不是数组类型

          • 虚拟机将N表明的全限定名传递给D的类加载器来加载C
          • C被成功加载后(多是以前已经加载过或者本次执行了首次加载),虚拟机将D中的符号引用N替换为C的直接引用
          • 符号引用验证,如D是否具有对C的访问权限
        • C是数组类型


          • 虚拟机将N表明的全限定名(如[Ljava.lang.Integer)传递给D的类加载器来加载C(详见数组类加载流程)

            • D的类加载器先加载C的组件类型(如java.lang.Integer)
            • 虚拟机在方法区生成一个表明了数组维度和组件类型的数组对象
          • C被成功加载后,虚拟机将D中的符号引用N替换为C的直接引用
          • 符号引用验证,如D是否具有对C的访问权限
    • 字段解析

      • 对CONSTANT_Fieldref_info符号引用的解析

        • 对class_index的解析
        • 对nameAndType_index的解析
      • 虚拟机在类D中加载字段符号引用N为字段F的直接引用

        • 虚拟机在N中指定的类C里寻找字段描述符和N中指定的字段描述符一致的字段F

          • 能找到,就将符号引用N替换为F的直接引用

            • 符号引用验证,如D是否具有对F的访问权限
          • 找不到,在类C实现的接口中按照继承关系从下向上寻找字段描述符和N中指定的字段描述符一致的字段F

            • 能找到就将符号引用N替换为F的直接引用

              • 符号引用验证,如D是否具有对F的访问权限
            • 找不到,在类C继承的父类中按照继承关系从下向上寻找字段描述符和N中指定的字段描述符一致的字段F

              • 能找到就将符号引用N替换为F的直接引用

                • 符号引用验证,如D是否具有对F的访问权限
              • 找不到,抛出java.lang.NoSuchFieldError异常
    • 类方法解析

      • 对CONSTANT_Methodref_info符号引用的解析

        • 对class_index的解析
        • 对nameAndType_index的解析
      • 虚拟机在类D中加载类方法符号引用N为方法M的直接引用

        • 虚拟机在N中指定的类C里寻找方法描述符和N中指定的方法描述符一致的方法M

          • 能找到

            • class_index指定的类不是接口,就将符号引用N替换为M的直接引用
            • class_index指定的类是接口,抛出java.lang.IncompatibleClassChangeError异常
            • 符号引用验证,如D是否具有对M的访问权限
          • 找不到,在类C继承的父类中按照继承关系从下向上寻找方法描述符和N中指定的方法描述符一致的方法M

            • 能找到就将符号引用N替换为M的直接引用

              • 符号引用验证,如D是否具有对M的访问权限
            • 找不到,在类C实现的接口中按照继承关系从下向上寻找方法描述符和N中指定的方法描述符一致的方法M

              • 能找到,说明类C是抽象类,抛出java.lang.AbstractMethodError异常(为何说明C是抽象类呢?C的方法在C中找不到,可是在C实现的接口中找到了,这意味着C实现了接口可是没有实现接口的这个方法,所以C类只多是抽象类。)
              • 找不到,抛出java.lang.NoSuchMethodError异常
    • 接口方法解析

      • 对CONSTANT_InterfaceMethodref_info符号引用的解析

        • 对class_index的解析
        • 对nameAndType_index的解析
      • 虚拟机在类D中加载接口方法符号引用N为方法M的直接引用

        • 虚拟机在N中指定的接口C里寻找方法描述符和N中指定的方法描述符一致的方法M

          • 能找到

            • class_index指定的类是C接口,就将符号引用N替换为M的直接引用
            • class_index指定的类C不是接口,抛出java.lang.IncompatibleClassChangeError异常
            • 符号引用验证,接口方法都是public的因此没有访问权限的问题
          • 找不到,在接口C继承的父接口中按照继承关系从下向上寻找方法描述符和N中指定的方法描述符一致的方法M

            • 能找到就将符号引用N替换为M的直接引用
            • 找不到,抛出java.lang.NoSuchMethodError异常

初始化 Initialization

初始化就是执行<clinit>()方法的过程

<clinit>()

  • <clinit>()是编译期生成在Class字节码中的,由编译器自动收集类中的全部类变量的赋值动做和静态代码块static{…}中的语句合并而成
  • <clinit>()是类构造器,与实例构造器<init>()不一样,虚拟机保证会在调用前先调用其父类的<clinit>(),所以不须要显式调用父类构造器
  • 父类的<clinit>()对类变量的赋值操做优先于子类的<clinit>()执行
  • <clinit>()并不是必需,若是类中无静态代码块或对类变量的赋值操做,那么编译器能够不生成<clinit>()方法,Class字节码中也就没有<clinit>()方法
  • 接口无静态代码块可是能够用类变量赋值操做,所以也会生成<clinit>方法,可是不须要先调用父接口的<clinit>()方法,只有父接口的类变量使用时才调用<clinit>()方法初始化父接口
  • 虚拟机会保证多线程环境下类的<clinit>()方法能够阻塞地调用,即线程T1调用类C的<clinit>()方法初始化C的过程当中,线程T2会阻塞而没法进入类C的<clinit>()方法中的

使用 Using

卸载 Unloading

相关文章
相关标签/搜索