之前已经介绍过字段解析,不过由于我的疏忽,丢了一部分不得不介绍的内容,那就是字段注入。举个例子如下:
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对应的位置存储值了。