类文件解析005

之前的文章介绍了class文件解析常量池的步骤.其属于ClassFileParser::parseClassFile的核心步骤的第三步.本文介绍第4-6步.

首先回顾一下ClassFileParser::parseClassFile的核心步骤:

  1. 读取魔数并进行校验其是否为0xCAFEBABE

  2. 读取class文件主,次版本号及验证,检查版本号支持是否支持,若不支持,则抛出java.lang.UnsupportedClassVersionError

  3. 解析常量池

  4. 读取访问标识

    1. 处理JVM_ACC_INTERFACE的后向兼容问题,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
    2. 验证类的修饰符并设置
  5. 读取当前类索引,并按索引在常量池项中找到当前类的全限定名

  6. 读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄

    1. 如果是为0的话,则检查其类名是否是java.lang.Object
    2. 检查其对应的类型为klass,检查其类型不是数组,如果是数组,则抛出异常
  7. 读取接口信息,接口类型包括本地接口和父类传递接口

    1. 如果接口不存在,则返回一个空数组
    2. 解析接口
  8. 解析类变量,并计算出域大小和偏移量(oop-map)信息,并根据域分配策略对字段存储顺序进行分配.这些信息都将在后续步骤填入instanceKlass对象中成为类信息的一部分

  9. 读取方法信息.根据从class中解析出的method信息创建了methodOop对象

  10. 解析父类

  11. 根据已解析的父类,方法,接口等信息得到vtable,itable 大小

  12. 计算字段的偏移量

  13. 创建当前类instanceKlass并按照上述步骤已解析好的信息为该对象赋值,在这期间,同时创建了JAVA镜像类(mirror)

  14. 解析类文件中的属性

  15. 通知类已加载,跟新perfdata计数器


验证访问标识

这部分的代码如下:

// 4. Access flags 读取访问标识
AccessFlags access_flags;
jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS; // (0x0001 | 0x0010 | 0x0020 | x0200 | 0x0400 | 0x2000 |0x4000 | x1000)

// 4.1  处理JVM_ACC_INTERFACE的后向兼容问题
if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
// Set abstract bit for old class files for backward compatibility 如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容
flags |= JVM_ACC_ABSTRACT;
}

// 4.2 验证类的修饰符并设置
verify_legal_class_modifiers(flags, CHECK_(nullHandle));
access_flags.set_flags(flags);

AccessFlags 内部持有一个名为_flags的字段,并定义了多个方法来判断是否符合访问标记.

此外,在hotspot/src/share/vm/utilities/accessFlags.hpp 中还定义了一个枚举,定义了各种类型的访问标记.

enum {
// See jvm.h for shared JVM_ACC_XXX access flags

// HotSpot-specific access flags

// flags actually put in .class file
JVM_ACC_WRITTEN_FLAGS           = 0x00007FFF,

// methodOop flags
JVM_ACC_MONITOR_MATCH           = 0x10000000,     // True if we know that monitorenter/monitorexit bytecodes match
JVM_ACC_HAS_MONITOR_BYTECODES   = 0x20000000,     // Method contains monitorenter/monitorexit bytecodes
JVM_ACC_HAS_LOOPS               = 0x40000000,     // Method has loops
JVM_ACC_LOOPS_FLAG_INIT         = (int)0x80000000,// The loop flag has been initialized
JVM_ACC_QUEUED                  = 0x01000000,     // Queued for compilation
JVM_ACC_NOT_C2_COMPILABLE       = 0x02000000,
JVM_ACC_NOT_C1_COMPILABLE       = 0x04000000,
JVM_ACC_NOT_OSR_COMPILABLE      = 0x08000000,
JVM_ACC_HAS_LINE_NUMBER_TABLE   = 0x00100000,
JVM_ACC_HAS_CHECKED_EXCEPTIONS  = 0x00400000,
JVM_ACC_HAS_JSRS                = 0x00800000,
JVM_ACC_IS_OLD                  = 0x00010000,     // RedefineClasses() has replaced this method
JVM_ACC_IS_OBSOLETE             = 0x00020000,     // RedefineClasses() has made method obsolete
JVM_ACC_IS_PREFIXED_NATIVE      = 0x00040000,     // JVMTI has prefixed this native method

JVM_MH_INVOKE_BITS           // = 0x10001100      // MethodHandle.invoke quasi-native
                              = (JVM_ACC_NATIVE | JVM_ACC_SYNTHETIC | JVM_ACC_MONITOR_MATCH),

// klassOop flags
JVM_ACC_HAS_MIRANDA_METHODS     = 0x10000000,     // True if this class has miranda methods in it's vtable
JVM_ACC_HAS_VANILLA_CONSTRUCTOR = 0x20000000,     // True if klass has a vanilla default constructor
JVM_ACC_HAS_FINALIZER           = 0x40000000,     // True if klass has a non-empty finalize() method
JVM_ACC_IS_CLONEABLE            = (int)0x80000000,// True if klass supports the Clonable interface
JVM_ACC_HAS_FINAL_METHOD        = 0x01000000,     // True if klass has final method

// klassOop and methodOop flags
JVM_ACC_HAS_LOCAL_VARIABLE_TABLE= 0x00200000,

JVM_ACC_PROMOTED_FLAGS          = 0x00200000,     // flags promoted from methods to the holding klass

// field flags
// Note: these flags must be defined in the low order 16 bits because
// instanceKlass only stores a ushort worth of information from the
// AccessFlags value.
// These bits must not conflict with any other field-related access flags
// (e.g., ACC_ENUM).
// Note that the class-related ACC_ANNOTATION bit conflicts with these flags.
JVM_ACC_FIELD_ACCESS_WATCHED       = 0x00002000,  // field access is watched by JVMTI
JVM_ACC_FIELD_MODIFICATION_WATCHED = 0x00008000,  // field modification is watched by JVMTI

                                                // flags accepted by set_field_flags()
JVM_ACC_FIELD_FLAGS                = 0x00008000 | JVM_ACC_WRITTEN_FLAGS

};

此处,首先获得到访问标识后,与JVM_RECOGNIZED_CLASS_MODIFIERS 进行与运算,得到最终的标记. JVM_RECOGNIZED_CLASS_MODIFIERS为宏,定义在hotspot/src/share/vm/prims/jvm.h 中,如下:

#define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC | \
                                        JVM_ACC_FINAL | \
                                        JVM_ACC_SUPER | \
                                        JVM_ACC_INTERFACE | \
                                        JVM_ACC_ABSTRACT | \
                                        JVM_ACC_ANNOTATION | \
                                        JVM_ACC_ENUM | \
                                        JVM_ACC_SYNTHETIC)

宏展开后的结果如下: (0x0001 | 0x0010 | 0x0020 | x0200 | 0x0400 | 0x2000 |0x4000 | x1000)

接下来,如果访问标记为JVM_ACC_INTERFACE,并且主版本号小于50,则同时设置JVM_ACC_ABSTRACT,保证后向兼容.

最后,验证类的修饰符是否格式正确,代码如下:

void ClassFileParser::verify_legal_class_modifiers(jint flags, TRAPS) {
  if (!_need_verify) { return; }

  const bool is_interface  = (flags & JVM_ACC_INTERFACE)  != 0;
  const bool is_abstract   = (flags & JVM_ACC_ABSTRACT)   != 0;
  const bool is_final      = (flags & JVM_ACC_FINAL)      != 0;
  const bool is_super      = (flags & JVM_ACC_SUPER)      != 0;
  const bool is_enum       = (flags & JVM_ACC_ENUM)       != 0;
  const bool is_annotation = (flags & JVM_ACC_ANNOTATION) != 0;
  const bool major_gte_15  = _major_version >= JAVA_1_5_VERSION;

  if ((is_abstract && is_final) ||
      (is_interface && !is_abstract) ||
      (is_interface && major_gte_15 && (is_super || is_enum)) ||
      (!is_interface && major_gte_15 && is_annotation)) {
    ResourceMark rm(THREAD);
    Exceptions::fthrow(
      THREAD_AND_LOCATION,
      vmSymbolHandles::java_lang_ClassFormatError(),
      "Illegal class modifiers in class %s: 0x%X",
      _class_name->as_C_string(), flags
    );
    return;
  }
}

其验证规则如下:

  1. 如果该类是抽象同时又是final的,则修饰符错误
  2. 如果该类是接口同时又不是抽象的,则修饰符错误
  3. 如果该类是接口,同时该类为(枚举或者含有JVM_ACC_SUPER访问标记),则修饰符错误
  4. 如果该类是注解,但不含有接口标记,则修饰符错误

验证当前类索引

这点在JAVA虚拟机规范的说明如下:

类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。

这里涉及的代码如下:

  // 读取当前类的索引
  u2 this_class_index = cfs->get_u2_fast();
  // 验证在常量池的指定索引处,其类型为CONSTANT_Class_info,验证索引值是有限的
  check_property(
    valid_cp_range(this_class_index, cp_size) &&
      cp->tag_at(this_class_index).is_unresolved_klass(),
    "Invalid this class index %u in constant pool in class file %s",
    this_class_index, CHECK_(nullHandle));
  
  // 
  symbolHandle class_name (THREAD, cp->unresolved_klass_at(this_class_index));
  assert(class_name.not_null(), "class_name can't be null");

  // It's important to set parsed_name *before* resolving the super class.
  // (it's used for cleanup by the caller if parsing fails)
  // 在解析父类之前保存parsed_name 是很重要的,如果解析失败的话,用来使调用者进行清理的
  parsed_name = class_name;

  // Update _class_name which could be null previously to be class_name 修改_class_name
  _class_name = class_name;

  // Don't need to check whether this class name is legal or not.
  // It has been checked when constant pool is parsed.
  // However, make sure it is not an array type.
  // 不需要检查类名是否合格.它已经在常量池解析的时候检查过了.但是,需要检查它不是数组类型
  if (_need_verify) {
    guarantee_property(class_name->byte_at(0) != JVM_SIGNATURE_ARRAY,
                       "Bad class name in class file %s",
                       CHECK_(nullHandle));
  }

  klassOop preserve_this_klass;   // for storing result across HandleMark 通过HandleMark来存储结构

  // release all handles when parsing is done 当解析完之后释放所有的handle
  { HandleMark hm(THREAD); // 定义在handles.hpp 中

    // Checks if name in class file matches requested name
    // 检查在类文件中名字是否和要求的名字匹配
    if (name.not_null() && class_name() != name()) {
      ResourceMark rm(THREAD);
      // 如果不匹配,则抛出java.lang.NoClassDefFoundError
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbolHandles::java_lang_NoClassDefFoundError(),
        "%s (wrong name: %s)",
        name->as_C_string(),
        class_name->as_C_string()
      );
      return nullHandle;
    }

    if (TraceClassLoadingPreorder) {
      tty->print("[Loading %s", name()->as_klass_external_name());
      if (cfs->source() != NULL) tty->print(" from %s", cfs->source());
      tty->print_cr("]");
    }

这里的代码比较简单,就不在展开了


解析父类索引

关于父类索引,jvm规范如下:

父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当前类的直接父类,以及它所有间接父类的access_flag中都不能带有ACC_FINAL标记。对于接口来说,它的Class文件的super_class项的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为代表java.lang.Object的CONSTANT_Class_info类型常量。如果Class文件的super_class的值为0,那这个Class文件只可能是定义的是java.lang.Object类,只有它是唯一没有父类的类。

这里涉及的代码如下:

// 6. 读取父类索引,并按索引在常量池中找到父类的全限定名和父类句柄
    u2 super_class_index = cfs->get_u2_fast();
    if (super_class_index == 0) {
    	// 6.1 如果是为0的话,则检查其类名是否是java.lang.Object
      check_property(class_name() == vmSymbols::java_lang_Object(),
                     "Invalid superclass index %u in class file %s",
                     super_class_index,
                     CHECK_(nullHandle));
    } else {
    	// 6.2 检查其对应的类型为klass
      check_property(valid_cp_range(super_class_index, cp_size) &&
                     is_klass_reference(cp, super_class_index),
                     "Invalid superclass index %u in class file %s",
                     super_class_index,
                     CHECK_(nullHandle));
      // The class name should be legal because it is checked when parsing constant pool.
      // However, make sure it is not an array type.
      //  检查其类型不是数组
      bool is_array = false;
      if (cp->tag_at(super_class_index).is_klass()) {
        super_klass = instanceKlassHandle(THREAD, cp->resolved_klass_at(super_class_index));
        if (_need_verify)
          is_array = super_klass->oop_is_array();
      } else if (_need_verify) {
        is_array = (cp->unresolved_klass_at(super_class_index)->byte_at(0) == JVM_SIGNATURE_ARRAY);
      }
      if (_need_verify) {
    	  // 如果是数组,则抛出异常
        guarantee_property(!is_array,
                          "Bad superclass name in class file %s", CHECK_(nullHandle));
      }
 }

同样,代码很简单,这里就不再展开了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值