zoukankan      html  css  js  c++  java
  • 解析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()方法解析类属性,方法的实现比较繁琐,只需要按照各属性的格式来解析即可,有兴趣的读者可自行研究。

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

    相关文章的链接如下:

    1、在Ubuntu 16.04上编译OpenJDK8的源代码 

    2、调试HotSpot源代码

    3、HotSpot项目结构 

    4、HotSpot的启动过程 

    5、HotSpot二分模型(1)

    6、HotSpot的类模型(2)  

    7、HotSpot的类模型(3) 

    8、HotSpot的类模型(4)

    9、HotSpot的对象模型(5)  

    10、HotSpot的对象模型(6) 

    11、操作句柄Handle(7)

    12、句柄Handle的释放(8)

    13、类加载器 

    14、类的双亲委派机制 

    15、核心类的预装载

    16、Java主类的装载  

    17、触发类的装载  

    18、类文件介绍 

    19、文件流 

    作者持续维护的个人博客classloading.com

    关注公众号,有HotSpot源码剖析系列文章!

        

      

  • 相关阅读:
    JDK、JRE、JVM的区别联系
    1.1 计算机基础知识 —— 二进制
    java--面试中遇到的各种比较区别
    springMVC 运行流程
    算法--常用排序和查找
    Java面试问题总结
    dpkg:error错误求解:——重复文件触发关注
    memcpy实现
    Palindrome Number
    no matching function for call to 'sort(...),
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13407609.html
Copyright © 2011-2022 走看看