本章学些目标

  1. jvm如何加载字节码文件?
  2. 字节码文件的内容如何被jvm读取?

1 类/接口加载机制

虚拟机的类/接口加载过程

虚拟机把描述类的数据从字节码加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类/接口

如图所示:

注意:jvm先把类/接口编译成字节码文件,然后在运行期间,从字节码文件中完成加载过程。

1 类加载的时机

1.1 类的生命周期

  1. 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载 、验证、准备、解析、初始化 、使用、卸载 七个阶段。
  2. 验证、准备、解析三个部分统称为连接(Linking)
  3. 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,解析可能先于初始化,这是由“运行时绑定特性”决定的

1.2 类初始化的时机

以下六种情况,必须进行类的初始化(包括加载、验证等阶段):(前提是类还没初始化)

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,包括:
    1. 使用new关键字实例化对象
    2. 读取或设置类的静态字段
    3. 调用类的静态方法
  2. 使用java.lang.reflect包的方法对类进行反射调用
  3. 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化主类
  5. 如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,则需要先触发其初始化。
  6. 如果接口定义了默认方法(default关键字),如果这个接口的实现类发生了初始化,那该接口要在其之前被初始化【todo:如何从内存层面理解?】

3 接口初始化的时机

接口的加载过程与类加载过程稍有不同,针对接口需要做一些特殊说明:

  1. 跟类的初始化过程基本一致。
  2. 接口跟类的初始化差异:
    1. 子类初始化时一定先初始化父类
    2. 接口在初始化时,并不要求其父接口已经初始化,只有在真正使用到父接口的时(如引用接口中定义的常量)才会初始化

4 类加载的过程

类加载的过程包括:加载、验证、准备、解析和初始化这五个阶段所执行的具体动作。

4.1 加载

虚拟机规范:加载过程需要完成这三件事:

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

通过验证阶段后,才会真正写入方法区

4.2 验证

  1. 确保Class文件的字节流信息符合Java虚拟机规范,保证安全。
  2. 优化方案:如果全部代码都已经被反复使用和验证过,在生产环境的实施阶段就可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。

验证过程4个阶段:

只有通过了【文件格式验证】阶段之后,这段字节流才被允许进入方法区中进行存储,所以后面的三个验证阶段全部是基于方法区的存储结构上进行的,不再直接读取字节流

  1. 文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
    1.  是否以魔数0xCAFEBABE开头
    2.  主、次版本号是否在当前虚拟机接受范围之内
    3.  常量池的常量中是否有不被支持的常量类型
    4.  指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
    5.  CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。
    6.  Class文件中各个部分及文件本身是否有被删除的或附加的其他信息
    7.  等等
  1. 元数据验证:对类的元数据信息进行语义校验,保证不存在与《Java语言规范》定义相悖的元数据信息
    1.  这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
    2.  这个类的父类是否继承了不允许被继承的类(被final修饰的类)
    3.  如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
    4.  类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方 法重载,例如方法参数都一致,但返回值类型却不同等)
  1. 字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的。第二阶段完成元数据校验,第三阶段对类的方法体(Code属性)进行校验分析
    1.  保证操作数栈的数据类型与指令代码序列能配合工作,例如不会出现“在操作栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中”这样的情况
    2.  保证任何跳转指令都不会跳转到方法体以外的字节码指令上
    3.  保证方法体中的类型转换总是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,或者其它类型,则是非法的。
  1. 符号引用验证:对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。(当前类依赖的信息(其它类)能否得到满足
    1. 符号引用中通过字符串描述的全限定名是否能找到对应的类
    2. 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
    3.  符号引用中的类、字段、方法的可访问性(private、protected、public、<package>)是否可被当 前类访问
    
    无法通过符号引用验证,虚拟机抛出java.lang.IncompatibleClassChangeError的子类异常,典型的如: java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。

4.3 准备

目标:

  1. 类变量分配内存并设置初始值(零值)
  2. 如果类变量的存在ConstantValue属性,那在准备阶段就会赋值(实际的值)(final修饰)

关于初始值的说明:

public static int value = 123;
//那变量value在准备阶段过后的初始值为0而不是123,因为这时尚未开始执行任何Java方法,把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值为123的动作要到类的初始化阶段才会被执行

4.4 解析

目标:

  1. Java虚拟机将常量池内的符号引用替换为直接引用

两者对比:

内存目标 形式
符号引用 与JVM的内存布局无关,引用的目标并不一定是已经加载到JVM内存中的内容 任何形式字面量
直接引用 和JVM实现的内存布局直接相关 指针、相对偏移量、能间接定位到目标的句柄

4.4.1 类或接口的解析

假设A存在对B类的引用,解析过程如下:

4.4.2 字段解析

4.4.3 方法解析

4.4.4 接口方法解析

4.5 初始化

待续

原文地址:http://www.cnblogs.com/knowledgeispower/p/16886933.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性