专注虚拟机与编译器研究

第4.3篇-解析Class文件

类文件解析的入口是ClassFileParser类中定义的parseClassFile()方法。上一小节得到了文件字节流stream后,接着会在ClassLoader::load_classfile()函数中调用parseClassFile()函数,调用的源代码实现如下:

源代码位置:src/share/vm/classfile/classLoader.cpp
instanceKlassHandle h;
if (stream != NULL) {
    // class file found, parse it
    ClassFileParser parser(stream);
    ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
    Handle protection_domain;
    TempNewSymbol parsed_name = NULL;
    instanceKlassHandle result =
                            parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h));
    // add to package table
    if (add_package(name, classpath_index, THREAD)) {
      h = result;
    }
}

另外还有一些函数也会在必要的时候调用parseClassFile()函数,如装载Java主类时调用的SystemDictionary::resolve_from_stream()函数等。

调用的parseClassFile()函数的实现如下:  

instanceKlassHandle parseClassFile(Symbol* name,
                                     ClassLoaderData* loader_data,
                                     Handle protection_domain,
                                     TempNewSymbol& parsed_name,
                                     bool verify,
                                     TRAPS) {
    KlassHandle no_host_klass;
    return parseClassFile(name, loader_data, protection_domain, no_host_klass, NULL, parsed_name, verify, THREAD);
}

调用的另外一个方法的原型如下:  

instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name,
                                                    ClassLoaderData* loader_data,
                                                    Handle protection_domain,
                                                    KlassHandle host_klass,
                                                    GrowableArray<Handle>* cp_patches,
                                                    TempNewSymbol& parsed_name,
                                                    bool verify,
                                                    TRAPS)

这个方法的实现太复杂,这里简单分几个步骤详细介绍。  

1.  解析魔数、主版本号与次版本号

ClassFileStream* cfs = stream();
...
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();
…
_major_version = major_version;
_minor_version = minor_version;

读取魔数主要是为了验证值是否为0xCAFEBABE。读取到Class文件的主、次版本号并保存到ClassFileParser实例的_major_version和_minor_version中。  

2.  解析访问标识 

// Access flags
AccessFlags access_flags;
jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS;

if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) {
    // Set abstract bit for old class files for backward compatibility
    flags |= JVM_ACC_ABSTRACT;
}
access_flags.set_flags(flags);

读取并验证访问标识,这个访问标识在进行字段及方法解析过程中会使用,主要用来判断这些字段或方法是定义在接口中还是类中。JVM_RECOGNIZED_CLASS_MODIFIERS是一个宏,定义如下:

#define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC     |    \
                                        JVM_ACC_FINAL      |    \
                                        JVM_ACC_SUPER      |    \  // 辅助invokespecial指令
                                        JVM_ACC_INTERFACE  |    \
                                        JVM_ACC_ABSTRACT   |    \
                                        JVM_ACC_ANNOTATION |    \
                                        JVM_ACC_ENUM       |    \
                                        JVM_ACC_SYNTHETIC)

最后一个标识符是由前端编译器(如Javac等)添加上去的,表示是合成的类型。

3.  解析当前类索引 

类索引(this_class)是一个u2类型的数据,类索引用于确定这个类的全限定名。类索引指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。

// This class and superclass
u2 this_class_index = cfs->get_u2_fast();

Symbol*  class_name  = cp->unresolved_klass_at(this_class_index);
assert(class_name != NULL, "class_name can't be null");

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

将读取到的当前类的名称保存到ClassFileParser实例的_class_name属性中。

调用的cp->unresolved_klass_at()方法的实现如下:

源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp

// 未连接的返回Symbol*
// This method should only be used with a cpool lock or during parsing or gc
Symbol* unresolved_klass_at(int which) {     // Temporary until actual use
	intptr_t* oaar = obj_at_addr_raw(which);
	Symbol* tmp = (Symbol*)OrderAccess::load_ptr_acquire(oaar);
    Symbol* s = CPSlot(tmp).get_symbol();
    // check that the klass is still unresolved.
    assert(tag_at(which).is_unresolved_klass(), "Corrupted constant pool");
    return s;
}

举个例子如下:

#3 = Class         #17        // TestClass
...
#17 = Utf8          TestClass 

类索引为0x0003,去常量池里找索引为3的类描述符,类描述符中的索引为17,再去找索引为17的字符串,就是“TestClass”。调用obj_at_addr_raw()方法找到的是一个指针,这个指针指向表示“TestClass”这个字符串的Symbol对象,也就是在解析常量池项时会将本来存储索引值17替换为存储指向Symbol对象的指针。 

调用的obj_at_addr_raw()方法的实现如下:

intptr_t*   obj_at_addr_raw(int which) const {
    assert(is_within_bounds(which), "index out of bounds");
    return (intptr_t*) &base()[which];
}
intptr_t*   base() const {
  return (intptr_t*) (
     (  (char*) this  ) + sizeof(ConstantPool)
  );
}

base()是ConstantPool中定义的方法,所以this指针指向当前ConstantPool对象在内存中的首地址,加上ConstantPool类本身需要占用的内存大小后,指针指向了常量池相关信息,这部分信息通常就是length个指针宽度的数组,其中length为常量池数量。通过(intptr_t*)&base()[which]获取到常量池索引which对应的值,对于上面的例子来说就是一个指向Symbol对象的指针。 

4.  解析父类索引

父类索引(super_class)是一个u2类型的数据,父类索引用于确定这个类的父类全限定名。由于java语言不允许多重继承,所以父类索引只有一个。父类索指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。

u2 super_class_index = cfs->get_u2_fast();
instanceKlassHandle super_klass = parse_super_class(super_class_index,CHECK_NULL);

调用的parse_super()方法的实现如下: 

instanceKlassHandle ClassFileParser::parse_super_class(int super_class_index,TRAPS) {

  instanceKlassHandle super_klass;
  if (super_class_index == 0) { // 当为java.lang.Object类时,没有父类
    check_property(_class_name == vmSymbols::java_lang_Object(),
                   "Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL);
  } else {
    check_property(valid_klass_reference_at(super_class_index),
                   "Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL);
    // 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;
    constantTag mytemp = _cp->tag_at(super_class_index);
    if (mytemp.is_klass()) {
       super_klass = instanceKlassHandle(THREAD, _cp->resolved_klass_at(super_class_index));
    }
  }
  return super_klass;
}

如果类已经连接,那么可通过super_class_index直接找到表示父类的InstanceKlass实例,否则返回的值就是NULL。 

resolved_klass_at()方法的实现如下:

源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp
// 已连接的返回Klass*
Klass* resolved_klass_at(int which) const {  // Used by Compiler
    // Must do an acquire here in case another thread resolved the klass
    // behind our back, lest we later load stale values thru the oop.
    Klass* tmp = (Klass*)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which));
    return CPSlot(tmp).get_klass(); 
} 

其中的CPSlot类的实现如下:

class CPSlot VALUE_OBJ_CLASS_SPEC {
  intptr_t  _ptr;
 public:
  CPSlot(intptr_t ptr): _ptr(ptr) {}
  CPSlot(Klass*   ptr): _ptr((intptr_t)ptr) {}
  CPSlot(Symbol*  ptr): _ptr((intptr_t)ptr | 1) {} // 或上1表示已经解析过了,Symbol*本来不需要解析

  intptr_t value()     { return _ptr; }
  bool is_resolved()   { return (_ptr & 1) == 0; }
  bool is_unresolved() { return (_ptr & 1) == 1; }

  Symbol* get_symbol() {
    assert(is_unresolved(), "bad call");
    return (Symbol*)(_ptr & ~1);
  }
  Klass* get_klass() {
    assert(is_resolved(), "bad call");
    return (Klass*)_ptr;
  }
};  

5.  解析实现接口 

接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值, 它的长度为 interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量,其中 0 ≤ i <interfaces_count。在interfaces[]数组中,成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对应的是源代码中最左边的接口。

u2 itfs_len = cfs->get_u2_fast();
Array<Klass*>* local_interfaces =
parse_interfaces(itfs_len, protection_domain, _class_name,&has_default_methods, CHECK_(nullHandle));

parse_interfaces()方法的实现如下:

Array<Klass*>* ClassFileParser::parse_interfaces(int     length,
                                                 Handle  protection_domain,
                                                 Symbol* class_name,
                                                 bool*   has_default_methods,
                                                 TRAPS
){
  if (length == 0) {
    _local_interfaces = Universe::the_empty_klass_array();
  } else {
    ClassFileStream* cfs = stream();
    _local_interfaces = MetadataFactory::new_array<Klass*>(_loader_data, length, NULL, CHECK_NULL);

    int index;
    for (index = 0; index < length; index++) {
      u2 interface_index = cfs->get_u2(CHECK_NULL);
      KlassHandle interf;

      if (_cp->tag_at(interface_index).is_klass()) {
        interf = KlassHandle(THREAD, _cp->resolved_klass_at(interface_index));
      } else {
        Symbol*  unresolved_klass  = _cp->klass_name_at(interface_index);

        Handle   class_loader(THREAD, _loader_data->class_loader());

        // Call resolve_super so classcircularity is checked
        Klass* k = SystemDictionary::resolve_super_or_fail(class_name,
                                                           unresolved_klass,
							   class_loader,
							   protection_domain,
                                                           false, CHECK_NULL);
        // 将表示接口的InstanceKlass实例封装为KlassHandle实例
        interf = KlassHandle(THREAD, k);
      }

      if (InstanceKlass::cast(interf())->has_default_methods()) {
         *has_default_methods = true;
      }
      _local_interfaces->at_put(index, interf());
    }

    if (!_need_verify || length <= 1) {
       return _local_interfaces;
    }
  }
  return _local_interfaces;
}

循环对类实现的每个接口进行处理,通过interface_index找到接口在C++类中的表示InstanceKlass实例,然后封装为KlassHandle后,存储到_local_interfaces数组中。需要注意的是,如何通过interface_index找到对应的InstanceKlass实例,如果接口索引在常量池中已经是对应的InstanceKlass实例,说明已经连接过了,直接通过_cp_resolved_klass_at()方法获取即可;如果只是一个字符串表示,需要调用SystemDictionary::resolve_super_or_fail()方法进行连接,这个方法在连接时会详细介绍,这里不做过多介绍。

klass_name_at()方法的实现如下:

Symbol* ConstantPool::klass_name_at(int which) {
  assert(tag_at(which).is_unresolved_klass() || tag_at(which).is_klass(),
         "Corrupted constant pool");
  // A resolved constantPool entry will contain a Klass*, otherwise a Symbol*.
  // It is not safe to rely on the tag bit's here, since we don't have a lock, and the entry and
  // tag is not updated atomicly.
  CPSlot entry = slot_at(which);
  if (entry.is_resolved()) { // 已经连接时,获取到的是指向InstanceKlass实例的指针
    // Already resolved - return entry's name.
    assert(entry.get_klass()->is_klass(), "must be");
    return entry.get_klass()->name();
  } else {  // 未连接时,获取到的是指向Symbol实例的指针
    assert(entry.is_unresolved(), "must be either symbol or klass");
    return entry.get_symbol();
  }
}

其中的slot_at()方法的实现如下:  

CPSlot slot_at(int which) {
    assert(is_within_bounds(which), "index out of bounds");
    // Uses volatile because the klass slot changes without a lock.
    volatile intptr_t adr = (intptr_t)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which));
    assert(adr != 0 || which == 0, "cp entry for klass should not be zero");
    return CPSlot(adr);
}

同样调用obj_at_addr_raw()方法,获取ConstantPool中对应索引处存储的值,然后封装为CPSlot对象返回即可。

6.  解析类属性

ClassAnnotationCollector parsed_annotations;
parse_classfile_attributes(&parsed_annotations, CHECK_(nullHandle));

调用parse_classfile_attributes()方法解析类属性,方法的实现比较繁琐,只需要按照各属性的格式来解析即可,有兴趣的读者可自行研究。

关于常量池、字段及方法的解析在后面将详细介绍,这里暂时不介绍。 

 

 

 

 

  

 

posted on 2020-07-31 07:31  鸠摩(马智)  阅读(1251)  评论(0编辑  收藏  举报

导航