深入理解JVM之四:类加载过程

      虚拟机类加载机制概念:虚拟机把描述类的数据的class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。首先,先来思考以下两个问题:

      (1)虚拟机如何加载class文件;

      (2)class文件中的信息进入到虚拟机后发生什么变化

     相信看完这篇博文对回答这两个问题会很容易以及对类加载过程有个深刻的理解。


     上图包括了类加载的全过程:加载、验证、准备、初始化、解析、使用、卸载。那么在什么情况下会执行类的加载过程呢?主要有以下四种情况会执行类的加载过程:

      >使用new去实例化一个对象、读取或设置一个静态字段、调用一个静态方法
      >对类进行反射调用
      >初始化一个类时其父类还没有初始化
      >虚拟机启动,用户需要指定一个要执行的主类等

类加载阶段

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

验证阶段

       这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求(java语言本身是相对安全的语言,但class文件并不一定要求用java源码编译而来)验证阶段大致上会完成下面4个阶段的验证动作:文件格式验证、元数据验证、字节码验证、符号引用验证。
      >文件格式验证:验证输入的文件流是否符合Class文件格式规范,这一步保证输入的文件流能够正确的解析并且存储在方法区之内。注意:通过这个阶段的验证,字符流才会真正的进入到方法区中存储的,后面的3个验证阶段是基于方法区的存储结构进行的不会真正的操作字节流。验证点:是否以魔术0xCAFEBABE开头、主次版本号是否在当前的虚拟机范围之内、常量池的常量是否有不被支持的常量类型。
      >元数据验证:这个阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求。验证点:这个类是否有父类、这个类的父类是否不允许继承、这个类是否是抽象类是否实现父类或接口之中要求的方法等
      >字节码验证:这个阶段的目的是通过数据流和控制流分析确定程序语义是否合法、符合逻辑。验证点:保证任意操作栈的数据类型和指令码序列都能配合工作、保证跳转指令不会跳转到方法体以外的字节码指令上等
      >符号引用验证:这一阶段可以看作是对类自身以外的信息进行匹配性校验,发生在虚拟机将符号引用转化为直接引用时。这个转化将在第三阶段--解析中发生。校验内容包括:符号引用中通过字符串的全限定名是否能找到对应吃的类、在指定的类中是否存在符合字段描述符以及简单名称所描述的方法和字段。

准备阶段

      这一阶段是正式为类变量分配内存并设置初始值阶段。注意两点:1、这个阶段进行内存分配仅仅是为类变量(static修饰的变量)而不包括实例变量,实例变量会在对象实例化时随着对象一起在java堆上分配内存;2、这里说的初始值通常说的是零值,真正的赋值操作是在初始化阶段,当然这边也有例外:public static final int value = 123这种情况下类字段的字段属性表存在constantValue属性,那么在准备阶段类变量的将被赋值。

解析阶段

      虚拟机将常量池中的符号引用替换为直接引用。符号引用和直接引用的区别:1、符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,引用的目标不一定在内存中已经加载。各种虚拟机实现的内存布局可以多种形式但是符号引用必须一致的,这也是为什么符号引用的字面量明确定义在虚拟机规范class文件中的原因。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符
     >类和接口解析:1、如果当前符号引用对应的类不是数组,虚拟机将符号引用代表的权限名传递该当前代码所处的类的加载器,在加载过程中由于元数据验证以及字节码验证的需要又会触发其他相关类的加载动作;2、若为数组且数组的元素类型为对象,首先按照前一个步骤加载数组中的元素类型,接着虚拟机生成一个代表数组维度和元素的数组对象;3在前面无异常情况下解析完成进行符号引用验证。
      >字段解析:1、对字段的class_index项中索引CONSTANT_class_info符号引用进行解析;2、如果这个字段对应类或接口本身包含该字段则直接返回该字段;3、否则,如果字段所属类实现类接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口;4、否则,如果该字段所属的类不是java.lang.object的话将会按照继承关系从下往上递归搜索其父类中是否存在该字段;5、否则,查找失败
      >类方法解析:1、也需要解析类方法表的class_index项中索引的方法所属的类或接口的符号引用;2、在该方法对应的类中查找是否有简单名称和描述符都相匹配的方法;3、否则,在该类的父类中查找是否有简单名称和描述符都相匹配的方法;4、否则,在该类的接口列表递归的查找是否有简单名称和描述符都相匹配的方法;5、否则‘查找失败。
      >接口方法解析:索引的对象是该方法处的接口或其父接口中

初始化阶段

      初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。初始化阶段是执行类构造器<clinit>()方法的过程。需要注意以下几点:1、<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块static{}块中语句的合并产生;2、<clinit>()方法与类的构造器(实例构造器init())不同,它不需要显示的调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法执行完毕;3、<clinit>()方法对于类或接口不是必须的;4、接口和类一样都会生成<clinit>()方法,但接口在<clinit>()方法执行不需要先执行父接口<clinit>()方法,只有当父接口中定义变量时使用;5、虚拟机会保证一个类<clinit>()方法在多线程环境中被正确的加锁和同步。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值