在ClassFileParser::parseClassFile()函数中计算vtable和itable所需要的大小,之前已经介绍过vtable大小的计算,这一篇将详细介绍itable大小的计算过程。调用语句如下:
// Size of Java itable (in words) if( access_flags.is_interface() ){ itable_size = 0 ; // 当为接口时,itable_size为0 }else{ itable_size = klassItable::compute_itable_size(_transitive_interfaces); }
由于ClassFileparser::parseClassFile()方法可能分析的是接口,所以要判断一下,当为接口时,并不存在itable,也就是只有类才有itable。调用KlassItable::compute_itable_size()函数计算所需要的大小,此方法的实现如下:
int klassItable::compute_itable_size(Array<Klass*>* transitive_interfaces) { // Count no of interfaces and total number of interface methods CountInterfacesClosure cic; visit_all_interfaces(transitive_interfaces, &cic); // There's alway an extra itable entry so we can null-terminate it. int itable_size = calc_itable_size(cic.nof_interfaces() + 1, cic.nof_methods()); return itable_size; }
调用visit_all_interfaces()函数计算类实现的所有接口的总数(包括直接与间接实现的接口)和接口中定义的所有方法,并通过CountInterfacesClosure类的_nof_interfaces与_nof_methods属性来保存。调用的visit_all_interfaces()函数的实现如下:
// Visit all interfaces with at least one itable method void visit_all_interfaces(Array<Klass*>* transitive_intf, InterfaceVisiterClosure *blk) { // Handle array argument for(int i = 0; i < transitive_intf->length(); i++) { Klass* intf = transitive_intf->at(i); // Find no. of itable methods int method_count = 0; // 将Klass类型的intf转换为InstanceKlass类型后调用methods()方法 Array<Method*>* methods = InstanceKlass::cast(intf)->methods(); if (methods->length() > 0) { for (int i = methods->length(); --i >= 0; ) { if (interface_method_needs_itable_index(methods->at(i))) { method_count++; } } } // Only count interfaces with at least one method if (method_count > 0) { blk->doit(intf, method_count); } } }
循环遍历每个接口中的每个方法,并调用interface_method_needs_itable_index()方法判断接口中声明的方法是否需要在itable中添加一个新的itableEntry(指itableOffsetEntry和itableMethodEntry)。如果当前接口中有方法需要新的itableEntry,那么会调用CountInterfacesClosure类中的doit()方法对接口和方法进行统计。
调用的interface_method_needs_itable_index()函数的实现如下:
inline bool interface_method_needs_itable_index(Method* m) { if (m->is_static()) return false; // e.g., Stream.empty if (m->is_initializer()) return false; // <init> or <clinit> // If an interface redeclares a method from java.lang.Object, // it should already have a vtable index, don't touch it. // e.g., CharSequence.toString (from initialize_vtable) // if (m->has_vtable_index()) return false; // NO! return true; }
接口默认也继承了Object类,所以也会继承来自Object的5个方法,不过这5个方法并不需要itableEntry,已经在vtable中有对应的vtableEntry,所以直接返回false即可。
在visit_all_interfaces()方法中会调用CountInterfacesClosure类的doit()方法,类及方法的实现如下:
class CountInterfacesClosure : public InterfaceVisiterClosure { private: int _nof_methods; int _nof_interfaces; public: CountInterfacesClosure() { _nof_methods = 0; _nof_interfaces = 0; } int nof_methods() const { return _nof_methods; } int nof_interfaces() const { return _nof_interfaces; } void doit(Klass* intf, int method_count) { _nof_methods += method_count;
_nof_interfaces++; } };
可以看到,doit()方法只是对接口数量和方法进行了简单的统计并保存到了_nof_interfaces与_nof_methods属性中。 这样后续就可以调用calc_itable_size()函数计算itable需要占用的空间大小了,方法的实现如下:
// Helper methods static int calc_itable_size(int num_interfaces, int num_methods) { return (num_interfaces * itableOffsetEntry::size()) + (num_methods * itableMethodEntry::size()); }
可以清楚的看到对于itable大小的计算逻辑,也就是接口占用的内存大小加上方法占用的内存大小之和。不过在compute_itable_size()方法中调用此方法时,num_interfaces为类实现的所有接口总数加1,所以最后一个是空的,这也是做为遍历接口的终止条件而存在。
计算好itable需要占用的内存大小后就可以部分初始化这个表中的一些信息了,接下来在parseClassFile()函数中有如下的调用语句:
// Initialize itable offset tables klassItable::setup_itable_offset_table(this_klass);
调用的setup_itable_offset_table()函数的实现如下:
// Fill out offset table and interface klasses into the itable space void klassItable::setup_itable_offset_table(instanceKlassHandle klass) { if (klass->itable_length() == 0){ return; } // Count no of interfaces and total number of interface methods CountInterfacesClosure cic; visit_all_interfaces(klass->transitive_interfaces(), &cic); int nof_methods = cic.nof_methods(); int nof_interfaces = cic.nof_interfaces(); // Add one extra entry so we can null-terminate the table nof_interfaces++; // Fill-out offset table itableOffsetEntry* ioe = (itableOffsetEntry*)klass->start_of_itable(); itableMethodEntry* ime = (itableMethodEntry*)(ioe + nof_interfaces); intptr_t* end = klass->end_of_itable(); // Visit all interfaces and initialize itable offset table SetupItableClosure sic((address)klass(), ioe, ime); visit_all_interfaces(klass->transitive_interfaces(), &sic); }
每一次调用visit_all_interfaces()方法计算出接口总数和需要itableEntry的方法总数,第二次调用visit_all_interfaces()去初始化itable中的相关信息,也就是在visit_all_interfaces()中调用doit()方法,不过这次调用的是SetupItableClosure类中定义的doit()方法。SetupItableClosure类的定义如下:
class SetupItableClosure : public InterfaceVisiterClosure { private: itableOffsetEntry* _offset_entry; // Klass* _interface; int _offset; itableMethodEntry* _method_entry; // Method* _method address _klass_begin; public: SetupItableClosure(address klass_begin, itableOffsetEntry* offset_entry, itableMethodEntry* method_entry) { _klass_begin = klass_begin; _offset_entry = offset_entry; _method_entry = method_entry; } itableMethodEntry* method_entry() const { return _method_entry; } void doit(Klass* intf, int method_count) { int offset = ((address)_method_entry) - _klass_begin; _offset_entry->initialize(intf, offset); // 初始化itableOffsetentry中的相关属性 _offset_entry++; // 指向下一个itableOffsetEntry _method_entry += method_count; // 指向下一个接口中存储方法的itableMethodEntry } };
对itableOffsetEntry中的_interface与_interface初始化,在之前已经介绍过itableOffsetEntry类及相关属性,这里不再介绍。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
28、方法解析
29、klassVtable与klassItable类的介绍
30、计算vtable的大小
作者持续维护的个人博客 classloading.com。
关注公众号,有HotSpot源码剖析系列文章!