0
点赞
收藏
分享

微信扫一扫

【jvm014】验证阶段jvm主要做了什么?

龙毓七七 2022-03-19 阅读 57
jvm

验证阶段jvm主要做了什么?

验证是连接的第一步.这一阶段的主要的目的是确保class的文件的二进制字节流中包含的信息符合java虚拟机规范的全部约束要求,保证这些信息被当做代码运行后,不会对虚拟机自身造成危害.

验证阶段是非常重要的,从代码量和耗费的执行性能的角度上来讲,验证阶段的工作量在虚拟机整个类加载过程中占比相当大.

验证主要分为四个验证:文件格式验证,元数据验证,字节码验证,符号引用验证

文件格式验证:

  • 是否以魔数开头
  • 主次版本号是否在当前虚拟机支持的范围内
  • 常量池中是否含有不被支持的常量类型(检查常量tag标志)
  • 指向常量的各种索引值中是否含有指向不存在的常量或者不符合类型的常量
  • constant_utf8_info型的常量是否存在不符合utf8编码的数据
  • class文件中各个部分以及文件本身是否有被删除的或者附加的其他信息

这个阶段是基于二进制字节流进行的,只有通过这个验证,二进制字节流才会到达方法区进行存储,后面的三个验证也是基于方法区的存储结构,不会直接读取字节流了

元数据验证:

  • 这个类是否包含父类(除了java.lang.object外,所有类都要有父类)
  • 这个类的父类是否继承了不允许被继承的类(final修饰的类)
  • 如果这个类不是抽象类,是否实现了其父类或者接口中要求实现的所有方法
  • 类中的字段,方法是否与父类中产生矛盾,(例如覆盖了父类的final字段,或者出现了不符合规范的方法重载)

字节码验证:

这一阶段是验证阶段最为复杂的阶段,主要目的是通过数据流分析和控制流分析,确定程序语义是合法的,符合逻辑的.这阶段主要是校验class文件的code属性.

  • 保证任意时刻操作数栈的数据类型和指令代码序列都能配合工作,例如不会出现类似于“在操作数栈放置了一个int类型的数据,使用的时候按long类型来加载入本地变量表中”这样的情况
  • 保证任何跳转指令都不会跳转到方法体以外的字节码指令上
  • 保证方法体之内的类型转换都是有效的,比如,可以把一个子类赋值给父类数据类型,这是安全的,但是把父类赋值给子类数据类型,甚至是毫无关系的数据类型,这是危险的,不合法的.

由于数据流和控制流的高度复杂性,为了避免过多的时间消耗,在jdk1.6之后的javac编译和java虚拟机里进行了一项联合优化,把尽可能多的校验移到javac编译器里进行.具体做法是给方法体code属性的属性表中添加一项StackMapTable的新属性,这个属性描述了方法体所有的基本块,开始时本地变量表和操作数栈应有的状态,在字节码验证期间,java虚拟机就不需要根据程序推导了,只需要检查StackMapTable的记录是否合法即可.这样字节码验证的类型推导就变成了类型检查,从而节省了大量的校验时间.

符号引用验证:

最后一个验证阶段发生在虚拟机将符号引用转化为直接引用的时候,这个过程在连接的第三个阶段解析阶段发生.

符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗讲就是验证该类是否缺少或者被禁止访问它依赖的某些外部类,方法,字段等资源.

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

符号引用的目的是确保解析行为能够正常执行,如果无法通过符号引用验证,java虚拟机将会抛出一个java.lang.IncompatibleClassChangeError的子类异常,典型的如:java.lang.IllegalAccessError,java.lang.NoSuchFieldError,java.lang.NoSuchMethodError等

举报

相关推荐

0 条评论