zoukankan      html  css  js  c++  java
  • 字段解析之字段注入

    之前已经介绍过字段解析,不过由于我的疏忽,丢了一部分不得不介绍的内容,那就是字段注入。举个例子如下:

    package jvmTest;
     
    import java.lang.management.ManagementFactory;
    import java.lang.management.RuntimeMXBean;
     
    class Base{
        public static int a=1;
     
        public static String s="abc";
     
        public static Integer a2=6;
     
        public static Integer a3=8;
     
        public static int a4=4;
     
        private int a5=12;
     
        private Integer a6=13;
     
        private int a7=13;
    }
     
    public class MainTest { 
        public static void main(String[] args) {
            Class a=Base.class;
            System.out.println(Base.a);
        }
    }
    

    在Class Browser中搜索java.lang.Class,第一个便是该类对应的Klass,如下图:

    点击该类可以发现该类其实是有很多属性的,如下:

    klass字段之前定义的私有属性对应着java.lang.Class类中定义的字段,从klass开始的剩余几个属性在源码中都没有,那这些属性是谁加进去的,什么时候加进去的了?答案是HotSpot,HotSpot在解析存储着java.lang.Class类的Class文件时就会注入一部分字段,关键代码在负责字段解析的ClassFileParser::parse_fields()方法中,如下:

    int num_injected = 0;
    InjectedField* injected = JavaClasses::get_injected(class_name, &num_injected);
    int total_fields = length + num_injected;
    

    将调用JavaClasses::get_injected()方法得到的注入字段的数量保存到num_injected中并记入总的字段数量total_fields中。调用的get_injected()方法的实现如下:

    InjectedField* JavaClasses::get_injected(Symbol* class_name, int* field_count) {
      *field_count = 0;
    
      vmSymbols::SID sid = vmSymbols::find_sid(class_name);
      if (sid == vmSymbols::NO_SID) {
        // Only well known classes can inject fields
        return NULL;
      }
    
      int count = 0;
      int start = -1;
    
    
    #define LOOKUP_INJECTED_FIELD(klass, name, signature, may_be_java) 
      if (sid == vmSymbols::VM_SYMBOL_ENUM_NAME(klass)) {              
        count++;                                                       
        if (start == -1) start = klass##_##name##_enum;                
      }
      ALL_INJECTED_FIELDS(LOOKUP_INJECTED_FIELD);
    #undef LOOKUP_INJECTED_FIELD
    
    
      if (start != -1) {
        *field_count = count;
        return _injected_fields + start;
      }
      return NULL;
    }

    ALL_INJECTED_FIELDS宏扩展后的结果如下:

    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_klass_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_array_klass_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_oop_size_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_static_oop_field_count_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_protection_domain_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_init_lock_enum;
    }
    if (sid == vmSymbols::java_lang_Class_enum) {
      count++;
      if (start == -1) start = java_lang_Class_signers_enum;
    }
    // ...

    count的值为7,表示有7个字段要注入,而start为java_lang_Class_klass_enum。_injected_fields数组如下:

    InjectedField JavaClasses::_injected_fields[] = {
       // ALL_INJECTED_FIELDS(DECLARE_INJECTED_FIELD)宏扩展后的结果如下:
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::klass_name_enum, vmSymbols::intptr_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::array_klass_name_enum, vmSymbols::intptr_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::oop_size_name_enum, vmSymbols::int_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::static_oop_field_count_name_enum, vmSymbols::int_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::protection_domain_name_enum, vmSymbols::object_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::init_lock_name_enum, vmSymbols::object_signature_enum, false },
       { SystemDictionary::java_lang_Class_knum,             vmSymbols::signers_name_enum, vmSymbols::object_signature_enum, false },
       // ...
    //  ALL_INJECTED_FIELDS(DECLARE_INJECTED_FIELD)
    };
    

    方法JavaClasses::get_injected()最后返回的是:

    { SystemDictionary::java_lang_Class_knum,vmSymbols::klass_name_enum, vmSymbols::intptr_signature_enum, false }
    

    继续在ClassFileParser::parse_fields()方法中处理,如下:

    // length就是解析Class文件中的字段数量
    int index = length;
    if (num_injected != 0) {
        for (int n = 0; n < num_injected; n++) {
          // Check for duplicates
          if (injected[n].may_be_java) {
            Symbol* name      = injected[n].name();
            Symbol* signature = injected[n].signature();
            bool duplicate = false;
            for (int i = 0; i < length; i++) {
              FieldInfo* f = FieldInfo::from_field_array(fa, i);
              if (name == _cp->symbol_at(f->name_index()) && signature == _cp->symbol_at(f->signature_index())) {
                // Symbol is desclared in Java so skip this one
                duplicate = true;
                break;
              }
            }
            if (duplicate) {
              // These will be removed from the field array at the end
              continue;
            }
          }
    
          // Injected field
          FieldInfo* field = FieldInfo::from_field_array(fa, index);
          field->initialize(JVM_ACC_FIELD_INTERNAL,
                            injected[n].name_index,
                            injected[n].signature_index,
                            0);
    
          BasicType type = FieldType::basic_type(injected[n].signature());
    
          // Remember how many oops we encountered and compute allocation type
          FieldAllocationType atype = fac->update(false, type);
          field->set_allocation_type(atype);
          index++;
        } // for循环结束
    }// if判断结束
    

    之前在介绍ClassFileParser::parse_fields()方法时,没有介绍注入字段的逻辑,如上就是字段注入的逻辑,和普通的类中定义的字段的处理逻辑类似。这样后续就会为需要注入的字段开辟内存存储空间,字段主要有:

    class java_lang_Class : AllStatic {
    
     private:
      // The fake offsets are added by the class loader when java.lang.Class is loaded
    
      static int _klass_offset;
      static int _array_klass_offset;
    
      static int _oop_size_offset;
      static int _static_oop_field_count_offset;
    
      static int _protection_domain_offset;
      static int _init_lock_offset;
      static int _signers_offset;
      //  ...
    }
    

    主要就是java_lang_Class中定义的这几个字段,在这里只所以定义java_lang_Class类并定义对应的字段主要还是为了方便操作内存中对应字段的信息,所以这个类中定义了许多的操作方法。这几个字段的初始化如下:

    void java_lang_Class::compute_offsets() {
       // ...
    
       java_lang_Class::_klass_offset                  = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_klass_enum);
       java_lang_Class::_array_klass_offset            = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_array_klass_enum);
       java_lang_Class::_oop_size_offset               = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_oop_size_enum);
       java_lang_Class::_static_oop_field_count_offset = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_static_oop_field_count_enum);
       java_lang_Class::_protection_domain_offset      = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_protection_domain_enum);
       java_lang_Class::_init_lock_offset              = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_init_lock_enum);
       java_lang_Class::_signers_offset                = JavaClasses::compute_injected_offset(JavaClasses::java_lang_Class_signers_enum);
    //  CLASS_INJECTED_FIELDS(INJECTED_FIELD_COMPUTE_OFFSET);
    }
    

    这个方法在java.lang.Class文件解析后调用,计算出这几个字段在instanceOop(表示Class对象)中的偏移,调用的compute_injected_offset()方法的实现如下:

    int JavaClasses::compute_injected_offset(InjectedFieldID id) {
      return _injected_fields[id].compute_offset();
    }
    
    int InjectedField::compute_offset() {
      Klass* klass_oop = klass();
      for (AllFieldStream fs(InstanceKlass::cast(klass_oop)); !fs.done(); fs.next()) {
        if (!may_be_java && !fs.access_flags().is_internal()) {
          // Only look at injected fields
          continue;
        }
        if (fs.name() == name() && fs.signature() == signature()) {
          return fs.offset();
        }
      }
      // ...
      return -1;
    }
    
    Klass* klass() const {
       return SystemDictionary::well_known_klass(klass_id);
    }
    

    获取注入字段在instanceOop(表示Class对象)的偏移并通过对应属性保存。这样我们在得到Class对象相关属性的值后就可以利用偏移直接设置到对应的内存位置上,如保存Class对象表示的InstanceKlass对象的_klass_offset属性的设置如下:

    java_lang_Class::set_klass(mirror, real_klass());
    

    其中的real_klass()会获取到Klass对象(是Class对象表示的Java类),mirror是oop对象(表示的是Class对象)调用set_klass()方法进行存储设置,如下:

    void java_lang_Class::set_klass(oop java_class, Klass* klass) {
      assert(java_lang_Class::is_instance(java_class), "must be a Class object");
      java_class->metadata_field_put(_klass_offset, klass);
    }
    
    inline void oopDesc::metadata_field_put(int offset, Metadata* value) {
      *metadata_field_addr(offset) = value;
    }
    
    inline Metadata** oopDesc::metadata_field_addr(int offset) const {
    	return (Metadata**)field_base(offset);
    }
    
    // field_base方法用于计算类实例字段的地址,offset是偏移量
    inline void* oopDesc::field_base(int offset)  const {
    	return (void*)&((char*)this)[offset];
    }
    

    知道了偏移,知道了设置的值,这样就可以根据偏移在java_class对应的位置存储值了。  

      

      

      

      

      

      

      

      

      

  • 相关阅读:
    Nginx和PHP-FPM的启动、重启、停止脚本分享
    [Linux]Fedora19修复被Windows干掉的引导
    [Linux]RHEL/CentOS6配置tomcat使用80端口(与httpd整合)
    [Linux]SAMBA共享打印机
    [Linux]配置Logwatch使用第三方smtp发送电子邮件
    [oVirt]在双网卡网络环境下使用oVirt LiveCD
    走进Linux世界主题讲座纪录
    mysql用户及权限复制
    记一次失败的K8S安装部署
    HTTP状态码与爬虫
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13469745.html
Copyright © 2011-2022 走看看