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