>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 13251 个阅读者 刷新本主题
 * 贴子主题:  类连接阶段的验证原理 回复文章 点赞(0)  收藏  
作者:flybird    发表时间:2024-04-03 11:10:22     消息  查看  搜索  好友  邮件  复制  引用

本文介绍一下字节码验证。HotSpot虚拟机其实会遵守《Java虚拟机规范》,对Class文件中包含的信息进行合法性验证,以保证虚拟机的安全。从整体上来看,验证阶段大致上会进行如下4方面的验证:

文件格式验证:包括魔数,版本号等;
元数据验证:对程序进行语义分析,如是否有父类,是否继承了不被继承的类,不是抽象类,是否实现了父类或者接口中的所有要求被实现的方法;
字节码验证:指令级别的语义验证,如跳转指令不会跳转到方法体以外的代码上;
符号引用验证:符号引用转化为直接引用的时候,可以看作是对类自身以外的信息进行匹配验证,如通过全限定名,是否能找到对应的类。

文件格式的验证大部分都会在解析类文件的parseClassFile()函数中进行,例如对魔数,版本号的验证,如下:
cfs->guarantee_more(8, CHECK_(nullHandle));  // magic, major, minor
u4 magic = cfs->get_u4_fast();
guarantee_property(magic == JAVA_CLASSFILE_MAGIC,"Incompatible magic value %u in class file %s",magic, CHECK_(nullHandle));

// Version numbers
u2 minor_version = cfs->get_u2_fast();
u2 major_version = cfs->get_u2_fast();

// Check version numbers - we check this even with verifier off
if (!is_supported_version(major_version, minor_version)) {
    if (name == NULL) {
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbols::java_lang_UnsupportedClassVersionError(),
        "Unsupported major.minor version %u.%u",
        major_version,
        minor_version);
    } else {
      ResourceMark rm(THREAD);
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbols::java_lang_UnsupportedClassVersionError(),
        "%s : Unsupported major.minor version %u.%u",
        name->as_C_string(),
        major_version,
        minor_version);
    }
    return nullHandle;
}

魔数验证了值,而版本号能够让当前虚拟机判断是否支持运行这个Class版本的文件。通常在读取文件中相关字节码时,都会调用guarantee_more()方法,以保证文件中有足够的字节信息用来读取。

元数据验证的逻辑也大部分都在类解析阶段完成,例如在parseClassFile()中对父类的验证逻辑如下:

if (super_klass.not_null()) {
  if (super_klass->is_interface()) {
    ResourceMark rm(THREAD);
    Exceptions::fthrow(
      THREAD_AND_LOCATION,
      vmSymbols::java_lang_IncompatibleClassChangeError(),
      "class %s has interface %s as super class",
      class_name->as_klass_external_name(),
      super_klass->external_name()
    );
    return nullHandle;
  }
  // Make sure super class is not final
  if (super_klass->is_final()) {
    THROW_MSG_(vmSymbols::java_lang_VerifyError(), "Cannot inherit from final class", nullHandle);
  }
}

验证父类不能为接口或有final修饰的类,否则将出现异常。 

在classFileParse.cpp文件中定义了一系列verify_xxx()或check_xxx()方法,都是对元数据进行验证的,有兴趣的读者可自行阅读。 

在InstanceKlass::link_class_impl()方法中调用verify_code()方法进行字节码验证,方法的实现如下:

bool InstanceKlass::verify_code(instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
  // 1) Verify the bytecodes
  Verifier::Mode mode = throw_verifyerror ? Verifier::ThrowException : Verifier::NoException;
  return Verifier::verify(this_oop, mode, this_oop->should_verify_class(), CHECK_false);
}

Verifier::verify()方法的实现如下:

bool Verifier::verify(instanceKlassHandle klass, Verifier::Mode mode, bool should_verify_class, TRAPS) {
  HandleMark hm;
  ResourceMark rm(THREAD);

  Symbol*        exception_name = NULL;
  const size_t   message_buffer_len = klass->name()->utf8_length() + 1024;
  char*          message_buffer = NEW_RESOURCE_ARRAY(char, message_buffer_len);
  char*          exception_message = message_buffer;

  const char* klassName = klass->external_name();
  // 失败回退,新的使用StackMapTable属性进行验证的叫类型检查,而之前的叫类型推导验证
  bool can_failover = FailOverToOldVerifier &&
                      klass->major_version() < NOFAILOVER_MAJOR_VERSION;

  // If the class should be verified, first see if we can use the split
  // verifier.  If not, or if verification fails and FailOverToOldVerifier
  // is set, then call the inference verifier.
  if (is_eligible_for_verification(klass, should_verify_class)) {
    // STACKMAP_ATTRIBUTE_MAJOR_VERSION的值为50
    if (klass->major_version() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) {
      ClassVerifier  split_verifier(klass, THREAD);
      split_verifier.verify_class(THREAD);
      exception_name = split_verifier.result();
      if (
            can_failover &&
            !HAS_PENDING_EXCEPTION &&
            (
               exception_name == vmSymbols::java_lang_VerifyError() ||
               exception_name == vmSymbols::java_lang_ClassFormatError()
            )
      ) {
          if (TraceClassInitialization || VerboseVerification) {
              tty->print_cr("Fail over class verification to old verifier for: %s", klassName);
          }
          // 如果失败,则回退到类型推导验证
          exception_name = inference_verify(klass, message_buffer, message_buffer_len, THREAD);
      }
      if (exception_name != NULL) {
          exception_message = split_verifier.exception_message();
      }
    } else {
        // 推导验证
        exception_name = inference_verify(klass, message_buffer, message_buffer_len, THREAD);
    }
  }

  // ...
}

由于数据流验证的高复杂性,虚拟机设计团队为了避免过多的时间消耗在字节码验证阶 段,在JDK 1.6之后的Javac编译器和Java虚拟机中进行了一项优化,给方法体的Code属性的 属性表中增加了一项名为“StackMapTable”的属性,这项属性描述了方法体中所有的基本块 (Basic Block,按照控制流拆分的代码块)开始时本地变量表和操作栈应有的状态,在字节 码验证期间,就不需要根据程序推导这些状态的合法性,只需要检查StackMapTable属性中 的记录是否合法即可。这样将字节码验证的类型推导转变为类型检查从而节省一些时间。

在JDK 1.6的HotSpot虚拟机中提供了-XX:-UseSplitVerifier选项来关闭这项优化,或者使用参数-XX:+FailOverToOldVerifier要求在类型校验失败的时候退回到旧的类型推导方式进 行校验。而在JDK 1.7之后,对于主版本号大于50的Class文件,使用类型检查来完成数据流 分析校验则是唯一的选择,不允许再退回到类型推导的校验方式。  

验证阶段不是必须的,如果代码运行已经稳定了之后,可以通过设置参数-Xverfy:none参数来关闭类验证,减少虚拟机的类加载时间,提高运行效率。


程序猿的技术大观园:www.javathinker.net
  Java面向对象编程-->图形用户界面(下)
  JavaWeb开发-->自定义JSP标签(Ⅰ)
  JSP与Hibernate开发-->映射组成关系
  Java网络编程-->通过JDBC API访问数据库
  精通Spring-->绑定表单
  Vue3开发-->通过Vuex进行状态管理
  为网站代码块pre标签增加一个复制代码按钮代码
  BIO、NIO和AIO的区别、三种IO的原理与用法
  Java关键字final、static使用总结
  Java关键字final、static使用总结
  深入分析synchronized实现原理
  正则表达式范例
  Java 入门实用代码:从 List列表中 截取子列表
  Java入门实用代码:自定义异常
  Java 入门实用代码:设置文件只读
  Java入门实用代码: 字符串格式化
  Java程序初始化顺序(一看就懂)
  通过Java读取Excel数据
  正则表达式【匹配非字母和数字】
  【Java 并发笔记】CountDownLatch 相关整理
  初学者该学哪种编程语言
  更多...
 IPIP: 已设置保密
树形列表:   
如果發現大多數[url=https://sabaca... kericnnoe 2024-04-03 11:10:22
1页 1条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。