字段解析之字段注入

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

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对应的位置存储值了。  

  

  

  

  

  

  

  

  

  

原文地址:https://www.cnblogs.com/mazhimazhi/p/13469745.html