类文件解析的入口是ClassFileParser类中定义的parseClassFile()方法。上一小节得到了文件字节流stream后,接着会在ClassLoader::load_classfile()函数中调用parseClassFile()函数,调用的源代码实现如下:
源代码位置:src/share/vm/classfile/classLoader.cpp instanceKlassHandle h; if (stream != NULL) { // class file found, parse it ClassFileParser parser(stream); ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); Handle protection_domain; TempNewSymbol parsed_name = NULL; instanceKlassHandle result = parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h)); // add to package table if (add_package(name, classpath_index, THREAD)) { h = result; } }
另外还有一些函数也会在必要的时候调用parseClassFile()函数,如装载Java主类时调用的SystemDictionary::resolve_from_stream()函数等。
调用的parseClassFile()函数的实现如下:
instanceKlassHandle parseClassFile(Symbol* name, ClassLoaderData* loader_data, Handle protection_domain, TempNewSymbol& parsed_name, bool verify, TRAPS) { KlassHandle no_host_klass; return parseClassFile(name, loader_data, protection_domain, no_host_klass, NULL, parsed_name, verify, THREAD); }
调用的另外一个方法的原型如下:
instanceKlassHandle ClassFileParser::parseClassFile(Symbol* name, ClassLoaderData* loader_data, Handle protection_domain, KlassHandle host_klass, GrowableArray* cp_patches, TempNewSymbol& parsed_name, bool verify, TRAPS)
这个方法的实现太复杂,这里简单分几个步骤详细介绍。
1. 解析魔数、主版本号与次版本号
ClassFileStream* cfs = stream(); ... u4 magic = cfs->get_u4_fast(); guarantee_property(magic == JAVA_CLASSFILE_MAGIC,"Incompatible magic value %u in class file %s",magic, CHECK_(nullHandle)); // Version numbers u2 minor_version = cfs->get_u2_fast(); u2 major_version = cfs->get_u2_fast(); … _major_version = major_version; _minor_version = minor_version;
读取魔数主要是为了验证值是否为0xCAFEBABE。读取到Class文件的主、次版本号并保存到ClassFileParser实例的_major_version和_minor_version中。
2. 解析访问标识
// Access flags AccessFlags access_flags; jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_CLASS_MODIFIERS; if ((flags & JVM_ACC_INTERFACE) && _major_version < JAVA_6_VERSION) { // Set abstract bit for old class files for backward compatibility flags |= JVM_ACC_ABSTRACT; } access_flags.set_flags(flags);
读取并验证访问标识,这个访问标识在进行字段及方法解析过程中会使用,主要用来判断这些字段或方法是定义在接口中还是类中。JVM_RECOGNIZED_CLASS_MODIFIERS是一个宏,定义如下:
#define JVM_RECOGNIZED_CLASS_MODIFIERS (JVM_ACC_PUBLIC | \ JVM_ACC_FINAL | \ JVM_ACC_SUPER | \ // 辅助invokespecial指令 JVM_ACC_INTERFACE | \ JVM_ACC_ABSTRACT | \ JVM_ACC_ANNOTATION | \ JVM_ACC_ENUM | \ JVM_ACC_SYNTHETIC)
最后一个标识符是由前端编译器(如Javac等)添加上去的,表示是合成的类型。
3. 解析当前类索引
类索引(this_class)是一个u2类型的数据,类索引用于确定这个类的全限定名。类索引指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。
// This class and superclass u2 this_class_index = cfs->get_u2_fast(); Symbol* class_name = cp->unresolved_klass_at(this_class_index); assert(class_name != NULL, "class_name can't be null"); // Update _class_name which could be null previously to be class_name _class_name = class_name;
将读取到的当前类的名称保存到ClassFileParser实例的_class_name属性中。
调用的cp->unresolved_klass_at()方法的实现如下:
源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp
// 未连接的返回Symbol* // This method should only be used with a cpool lock or during parsing or gc Symbol* unresolved_klass_at(int which) { // Temporary until actual use intptr_t* oaar = obj_at_addr_raw(which); Symbol* tmp = (Symbol*)OrderAccess::load_ptr_acquire(oaar); Symbol* s = CPSlot(tmp).get_symbol(); // check that the klass is still unresolved. assert(tag_at(which).is_unresolved_klass(), "Corrupted constant pool"); return s; }
举个例子如下:
#3 = Class #17 // TestClass ... #17 = Utf8 TestClass
类索引为0x0003,去常量池里找索引为3的类描述符,类描述符中的索引为17,再去找索引为17的字符串,就是“TestClass”。调用obj_at_addr_raw()方法找到的是一个指针,这个指针指向表示“TestClass”这个字符串的Symbol对象,也就是在解析常量池项时会将本来存储索引值17替换为存储指向Symbol对象的指针。
调用的obj_at_addr_raw()方法的实现如下:
intptr_t* obj_at_addr_raw(int which) const { assert(is_within_bounds(which), "index out of bounds"); return (intptr_t*) &base()[which]; } intptr_t* base() const { return (intptr_t*) ( ( (char*) this ) + sizeof(ConstantPool) ); }
base()是ConstantPool中定义的方法,所以this指针指向当前ConstantPool对象在内存中的首地址,加上ConstantPool类本身需要占用的内存大小后,指针指向了常量池相关信息,这部分信息通常就是length个指针宽度的数组,其中length为常量池数量。通过(intptr_t*)&base()[which]获取到常量池索引which对应的值,对于上面的例子来说就是一个指向Symbol对象的指针。
4. 解析父类索引
父类索引(super_class)是一个u2类型的数据,父类索引用于确定这个类的父类全限定名。由于java语言不允许多重继承,所以父类索引只有一个。父类索指向常量池中类型为CONSTANT_Class_info的类描述符,再通过类描述符中的索引值找到常量池中类型为CONSTANT_Utf8_info的字符串。
u2 super_class_index = cfs->get_u2_fast(); instanceKlassHandle super_klass = parse_super_class(super_class_index,CHECK_NULL);
调用的parse_super()方法的实现如下:
instanceKlassHandle ClassFileParser::parse_super_class(int super_class_index,TRAPS) { instanceKlassHandle super_klass; if (super_class_index == 0) { // 当为java.lang.Object类时,没有父类 check_property(_class_name == vmSymbols::java_lang_Object(), "Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL); } else { check_property(valid_klass_reference_at(super_class_index), "Invalid superclass index %u in class file %s",super_class_index,CHECK_NULL); // The class name should be legal because it is checked when parsing constant pool. // However, make sure it is not an array type. bool is_array = false; constantTag mytemp = _cp->tag_at(super_class_index); if (mytemp.is_klass()) { super_klass = instanceKlassHandle(THREAD, _cp->resolved_klass_at(super_class_index)); } } return super_klass; }
如果类已经连接,那么可通过super_class_index直接找到表示父类的InstanceKlass实例,否则返回的值就是NULL。
resolved_klass_at()方法的实现如下:
源代码位置:/hotspot/src/share/vm/oops/constantPool.hpp // 已连接的返回Klass* Klass* resolved_klass_at(int which) const { // Used by Compiler // Must do an acquire here in case another thread resolved the klass // behind our back, lest we later load stale values thru the oop. Klass* tmp = (Klass*)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which)); return CPSlot(tmp).get_klass(); }
其中的CPSlot类的实现如下:
class CPSlot VALUE_OBJ_CLASS_SPEC { intptr_t _ptr; public: CPSlot(intptr_t ptr): _ptr(ptr) {} CPSlot(Klass* ptr): _ptr((intptr_t)ptr) {} CPSlot(Symbol* ptr): _ptr((intptr_t)ptr | 1) {} // 或上1表示已经解析过了,Symbol*本来不需要解析 intptr_t value() { return _ptr; } bool is_resolved() { return (_ptr & 1) == 0; } bool is_unresolved() { return (_ptr & 1) == 1; } Symbol* get_symbol() { assert(is_unresolved(), "bad call"); return (Symbol*)(_ptr & ~1); } Klass* get_klass() { assert(is_resolved(), "bad call"); return (Klass*)_ptr; } };
5. 解析实现接口
接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值, 它的长度为 interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量,其中 0 ≤ i parse_interfaces()方法的实现如下: 循环对类实现的每个接口进行处理,通过interface_index找到接口在C++类中的表示InstanceKlass实例,然后封装为KlassHandle后,存储到_local_interfaces数组中。需要注意的是,如何通过interface_index找到对应的InstanceKlass实例,如果接口索引在常量池中已经是对应的InstanceKlass实例,说明已经连接过了,直接通过_cp_resolved_klass_at()方法获取即可;如果只是一个字符串表示,需要调用SystemDictionary::resolve_super_or_fail()方法进行连接,这个方法在连接时会详细介绍,这里不做过多介绍。 klass_name_at()方法的实现如下: 其中的slot_at()方法的实现如下: 同样调用obj_at_addr_raw()方法,获取ConstantPool中对应索引处存储的值,然后封装为CPSlot对象返回即可。 调用parse_classfile_attributes()方法解析类属性,方法的实现比较繁琐,只需要按照各属性的格式来解析即可,有兴趣的读者可自行研究。 关于常量池、字段及方法的解析在后面将详细介绍,这里暂时不介绍。 相关文章的链接如下: 1、在Ubuntu 16.04上编译OpenJDK8的源代码 2、调试HotSpot源代码 3、HotSpot项目结构 4、HotSpot的启动过程 5、HotSpot二分模型(1) 6、HotSpot的类模型(2) 7、HotSpot的类模型(3) 8、HotSpot的类模型(4) 9、HotSpot的对象模型(5) 10、HotSpot的对象模型(6) 11、操作句柄Handle(7) 12、句柄Handle的释放(8) 13、类加载器 14、类的双亲委派机制 15、核心类的预装载 16、Java主类的装载 17、触发类的装载 18、类文件介绍 19、文件流 作者持续维护的个人博客classloading.com。 关注公众号,有HotSpot源码剖析系列文章! u2 itfs_len = cfs->get_u2_fast();
Array
Array
Symbol* ConstantPool::klass_name_at(int which) {
assert(tag_at(which).is_unresolved_klass() || tag_at(which).is_klass(),
"Corrupted constant pool");
// A resolved constantPool entry will contain a Klass*, otherwise a Symbol*.
// It is not safe to rely on the tag bit's here, since we don't have a lock, and the entry and
// tag is not updated atomicly.
CPSlot entry = slot_at(which);
if (entry.is_resolved()) { // 已经连接时,获取到的是指向InstanceKlass实例的指针
// Already resolved - return entry's name.
assert(entry.get_klass()->is_klass(), "must be");
return entry.get_klass()->name();
} else { // 未连接时,获取到的是指向Symbol实例的指针
assert(entry.is_unresolved(), "must be either symbol or klass");
return entry.get_symbol();
}
}
CPSlot slot_at(int which) {
assert(is_within_bounds(which), "index out of bounds");
// Uses volatile because the klass slot changes without a lock.
volatile intptr_t adr = (intptr_t)OrderAccess::load_ptr_acquire(obj_at_addr_raw(which));
assert(adr != 0 || which == 0, "cp entry for klass should not be zero");
return CPSlot(adr);
}
6. 解析类属性
ClassAnnotationCollector parsed_annotations;
parse_classfile_attributes(&parsed_annotations, CHECK_(nullHandle));