原创

字段解析之字段注入

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

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,如下图:
file

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

属性的领衔从12开始,这是因为在开启指针压缩情况下,对象头占用12字节。

在java.lang.Class类中共定义如下字段:

=== ANNOTATION  I  
=== ENUM  I  
=== SYNTHETIC  I  
=== cachedConstructor  Ljava/lang/reflect/Constructor;  
=== newInstanceCallerCache  Ljava/lang/Class;  
=== name  Ljava/lang/String;  
=== allPermDomain  Ljava/security/ProtectionDomain;  
=== useCaches  Z  
=== reflectionData  Ljava/lang/ref/SoftReference;  
=== classRedefinedCount  I  
=== genericInfo  Lsun/reflect/generics/repository/ClassRepository;  
=== serialVersionUID  J  
=== serialPersistentFields  [Ljava/io/ObjectStreamField;  
=== reflectionFactory  Lsun/reflect/ReflectionFactory;  
=== initted  Z  
=== enumConstants  [Ljava/lang/Object;  
=== enumConstantDirectory  Ljava/util/Map;  
=== annotationData  Ljava/lang/Class$AnnotationData;  
=== annotationType  Lsun/reflect/annotation/AnnotationType;  
=== classValueMap  Ljava/lang/ClassValue$ClassValueMap;

共有20个,然后还有5个注入字段。打开命令-XX:+PrintFieldLayout后的打印结果如下:

非静态的布局如下:

java.lang.Class: field layout
  @ 12 --- instance fields start ---
  @ 12 "cachedConstructor" Ljava.lang.reflect.Constructor;
  @ 16 "newInstanceCallerCache" Ljava.lang.Class;
  @ 20 "name" Ljava.lang.String;
  @ 24 "reflectionData" Ljava.lang.ref.SoftReference;  
  @ 28 "genericInfo" Lsun.reflect.generics.repository.ClassRepository;
  @ 32 "enumConstants" [Ljava.lang.Object;
  @ 36 "enumConstantDirectory" Ljava.util.Map;
  @ 40 "annotationData" Ljava.lang.Class$AnnotationData;
  @ 44 "annotationType" Lsun.reflect.annotation.AnnotationType;
  @ 48 "classValueMap" Ljava.lang.ClassValue$ClassValueMap;
  @ 52 "protection_domain" Ljava.lang.Object;
  @ 56 "init_lock" Ljava.lang.Object;
  @ 60 "signers_name" Ljava.lang.Object;
  @ 64 "klass" J
  @ 72 "array_klass" J 
  @ 80 "classRedefinedCount" I
  @ 84 "oop_size" I
  @ 88 "static_oop_field_count" I
  @ 92 --- instance fields end ---
  @ 96 --- instance ends ---

静态的布局如下:

@  0 --- static fields start ---
@  0 "allPermDomain" Ljava.security.ProtectionDomain;
@  4 "serialPersistentFields" [Ljava.io.ObjectStreamField;
@  8 "reflectionFactory" Lsun.reflect.ReflectionFactory;
@ 16 "serialVersionUID" J
@ 24 "ANNOTATION" I
@ 28 "ENUM" I
@ 32 "SYNTHETIC" I
@ 36 "useCaches" Z
@ 37 "initted" Z
@ 40 --- static fields end ---

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

正文到此结束