目录
1.1、Class实例中oop_size、klass等属性是哪来的?
1.3 为什么从_offset_of_static_fields处开始遍历?
4、oopDesc::adjust_pointers / follow_contents
本篇博客继续上一篇《Hotspot 垃圾回收之oop_iterate(一) 源码解析》讲解其他Klass子类的oop_oop_iterate方法的实现细节和同样是GC支持的oopDesc::adjust_pointers / follow_contents方法的实现。
1、java.lang.Class
理解InstanceMirrorKlass的引用遍历逻辑,我们需要逐步弄清楚以下几个问题:
1.1、Class实例中oop_size、klass等属性是哪来的?
查看java.lang.Class的源码可知,该类并没有oop_size、klass等属性的声明,但是通过HSDB查看该类的Klass确实有该属性,测试用例如下:
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);
while (true){
try {
System.out.println(getProcessID());
Thread.sleep(600*1000);
} catch (Exception e) {
}
}
}
public static final int getProcessID() {
RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
System.out.println(runtimeMXBean.getName());
return Integer.valueOf(runtimeMXBean.getName().split("@")[0])
.intValue();
}
}
在Class Browser中搜索java.lang.Class,第一个便是该类对应的Klass,如下图:
点击该类可以发现该类其实是有很多属性的,如下:
上述private开头的属性在源码中都可以找到,就是比较分散隐蔽,从klass开始的剩余几个属性在源码中都没有,那这些属性是谁加进去的,什么时候加进去的了?答案是JVM,JVM在解析class文件中包含的属性时判断是java.lang.Class就会注入一部分字段放到属性的解析结果Array<u2>中,关键代码在负责字段解析的ClassFileParser::parse_fields方法中,如下图:
其中JavaClasses::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)) { \
//如果klass一致则增加count
count++; \
//如果start未初始化,则初始化,表示数组的起始位置
if (start == -1) start = klass##_##name##_enum; \
}
ALL_INJECTED_FIELDS(LOOKUP_INJECTED_FIELD);
#undef LOOKUP_INJECTED_FIELD
if (start != -1) {
//如果找到了,将field_count置为count
*field_count = count;
//返回_injected_fields数组中start处开始的元素,元素个数就是count
return _injected_fields + start;
}
return NULL;
}
#define ALL_INJECTED_FIELDS(macro) \
CLASS_INJECTED_FIELDS(macro) \
CLASSLOADER_INJECTED_FIELDS(macro) \
MEMBERNAME_INJECTED_FIELDS(macro)
//java.lang.Class中新增的属性
#define CLASS_INJECTED_FIELDS(macro) \
macro(java_lang_Class, klass, intptr_signature, false) \
macro(java_lang_Class, array_klass, intptr_signature, false) \
macro(java_lang_Class, oop_size, int_signature, false) \
macro(java_lang_Class, static_oop_field_count, int_signature, false) \
macro(java_lang_Class, protection_domain, object_signature, false) \
macro(java_lang_Class, init_lock, object_signature, false) \
macro(java_lang_Class, signers, object_signature, false)
//_injected_fields是一个InjectedField数组
InjectedField JavaClasses::_injected_fields[] = {
ALL_INJECTED_FIELDS(DECLARE_INJECTED_FIELD)
};
#define DECLARE_INJECTED_FIELD(klass, name, signature, may_be_java) \
{ SystemDictionary::WK_KLASS_ENUM_NAME(klass), vmSymbols::VM_SYMBOL_ENUM_NAME(name##_name), vmSymbols::VM_SYMBOL_ENUM_NAME(signature), may_be_java },
从源码分析可知,处java.lang.Class外还有一些类也会以同样的方式注入新的属性,如下:
#define CLASSLOADER_INJECTED_FIELDS(macro) \
macro(java_lang_ClassLoader, loader_data, intptr_signature, false)
#define MEMBERNAME_INJECTED_FIELDS(macro) \
macro(java_lang_invoke_MemberName, vmloader, object_signature, false) \
macro(java_lang_invoke_MemberName, vmindex, intptr_signature, false) \
macro(java_lang_invoke_MemberName, vmtarget, intptr_signature, false)
1.2、_offset_of_static_fields
InstanceMirrorKlass就增加了一个静态属性_offset_of_static_fields,用来描述静态字段的起始偏移量,因为是静态的,无法在HSDB中直接查看该属性。该属性是通过init_offset_of_static_fields方法初始化的,其实现如下:
static void init_offset_of_static_fields() {
//_offset_of_static_fields是静态字段,未初始化时系统自动赋值0
assert(_offset_of_static_fields == 0, "once");
//SystemDictionary::Class_klass()就是全局唯一的InstanceMirrorKlass实例
_offset_of_static_fields = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->size_helper() << LogHeapWordSize;
}
int size_helper() const {
return layout_helper_to_size_helper(layout_helper());
}
static int layout_helper_to_size_helper(jint lh) {
assert(lh > (jint)_lh_neutral_value, "must be instance");
return lh >> LogHeapWordSize;
}
int layout_helper() const { return _layout_helper; }
InstanceMirrorKlass的_layout_helper的取值是105,如下:
因此_offset_of_static_fields的值就是104。从上一节的Class包含的字段属性来看,该类的实例按照对象头和属性算出来的大小应该是100,但是必须按照一个字段对应的字节数即8对齐,因此是104,_layout_helper的值是为了保证通过size_helper方法计算的实例大小能够不低于实际的大小。
该方法的调用链如下:
1.3 为什么从_offset_of_static_fields处开始遍历?
_offset_of_static_fields描述的是静态字段的起始偏移量,并非静态引用类型字段的起始偏移量,为什么要从这个偏移处开始遍历了?答案是Class实例为了确保通用性,将静态的引用类型属性都放在了一起且是静态字段区域的起始位置,然后通过static_oop_field_count属性就可以准确的定位所包含的oop了,这个跟普通类的实例完全相反,普通类实例的引用类型属性都是放在实例内存区域的最后,下面以上述的测试用例来说明。
先在Stack Memory中找到变量a即Base.class的地址,如下图:
然后在CHSDB中查看该地址对应的对象,如下:
该实例的大小是128,这个大小不是通过sizeof得出来的,而是从该oop的oop_size属性中读取出来的,用mem查看接下来的128字节即16个字段的内存数据,如下:
因为intel存储数据时是按照小端存储的,所以最后4行的真实内存数据应该如下:
0x00000000d69d9d78: 0x00000003 00000000
0x00000000d69d9d80: 0xd69d9dc0 d687bde0
0x00000000d69d9d88: 0xd687be00 00000001
0x00000000d69d9d90: 0x00000004 00000000
倒数第四行就是偏移量