在ClassFileParser::parseClassFile()函数中解析完字段并完成每个字段的布局后,会继续对方法进行解析,相关的处理语句如下:
bool has_final_method = false; AccessFlags promoted_flags; promoted_flags.set_flags(0); Array<Method*>* methods = parse_methods(access_flags.is_interface(), &promoted_flags, &has_final_method, &has_default_methods, CHECK_(nullHandle));
调用的parse_methods()函数的实现如下:
Array<Method*>* ClassFileParser::parse_methods(bool is_interface, AccessFlags* promoted_flags, bool* has_final_method, bool* has_default_methods, TRAPS) { ClassFileStream* cfs = stream(); cfs->guarantee_more(2, CHECK_NULL); // length u2 length = cfs->get_u2_fast(); if (length == 0) { _methods = Universe::the_empty_method_array(); } else { _methods = MetadataFactory::new_array<Method*>(_loader_data, length, NULL, CHECK_NULL); HandleMark hm(THREAD); for (int index = 0; index < length; index++) { methodHandle method = parse_method(is_interface,promoted_flags,CHECK_NULL); if (method->is_final()) { *has_final_method = true; // 如果定义了final方法,那么has_final_method变量的值为true } if (is_interface && !(*has_default_methods) && !method->is_abstract() && !method->is_static() && !method->is_private()) { // default method *has_default_methods = true; // 如果有默认的方法,则has_default_methods变量的值为true } _methods->at_put(index, method()); } } return _methods; }
计算出来的has_final_method与has_default_methods属性的值最终会存储到表示类的InstanceKlass对象的_misc_flags和_access_flags属性中,供其它地方使用。
对每个Java方法调用parse_method()函数进行解析,返回表示方法的Method对象,不过Method对象需要通过methodHandle句柄来操作,所以最终返回的是methodHandle句柄,然后存储到_methods数组中。
Java虚拟机规范规定的方法的格式如下:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
parse_method()方法会按照如上的格式读取各个属性值。首先看读取access_flags、name_index与descriptor_index的实现,如下:
methodHandle ClassFileParser::parse_method(bool is_interface,AccessFlags *promoted_flags,TRAPS) { ClassFileStream* cfs = stream(); methodHandle nullHandle; ResourceMark rm(THREAD); // Parse fixed parts cfs->guarantee_more(8, CHECK_(nullHandle)); // access_flags, name_index, descriptor_index, attributes_count int flags = cfs->get_u2_fast(); // 也就是规范中的access_flags属性 u2 name_index = cfs->get_u2_fast(); // 也就是规范中的name_index属性 Symbol* name = _cp->symbol_at(name_index); u2 signature_index = cfs->get_u2_fast(); // 也就是规范中的descriptor_index属性 Symbol* signature = _cp->symbol_at(signature_index); // 读取属性的数量,也就是规范中的attributes_count属性 u2 method_attributes_count = cfs->get_u2_fast(); ... }
接下来会在parse_method()函数中对属性进行解析,由于方法的属性较多且有些属性并不影响程序的运行,所以我们只对重要的属性Code进行解读,Code属性的格式如下:
Code_attribute { // Code属性的格式 u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
在parse_method()函数中会按照这种格式来读取信息,相关的源代码实现如下:
while (method_attributes_count--) { u2 method_attribute_name_index = cfs->get_u2_fast(); u4 method_attribute_length = cfs->get_u4_fast(); Symbol* method_attribute_name = _cp->symbol_at(method_attribute_name_index);
// 解析Code属性 if (method_attribute_name == vmSymbols::tag_code()) { // 读取max_stack、max_locals和code_length属性 if (_major_version == 45 && _minor_version <= 2) { max_stack = cfs->get_u1_fast(); max_locals = cfs->get_u1_fast(); code_length = cfs->get_u2_fast(); } else { max_stack = cfs->get_u2_fast(); max_locals = cfs->get_u2_fast(); code_length = cfs->get_u4_fast(); } // 读取code[code_length]数组的首地址 code_start = cfs->get_u1_buffer();
// 跳过code_length个u1类型的数据,也就是跳转code[code_length]数组 cfs->skip_u1_fast(code_length); // 读取exception_table_length属性并处理exception_table[exception_table_length] exception_table_length = cfs->get_u2_fast(); if (exception_table_length > 0) { exception_table_start = parse_exception_table(code_length, exception_table_length, CHECK_(nullHandle)); } // 读取attributes_count属性并处理attribute_info_attributes[attributes_count]数组 u2 code_attributes_count = cfs->get_u2_fast(); // ... while (code_attributes_count--) { u2 code_attribute_name_index = cfs->get_u2_fast(); u4 code_attribute_length = cfs->get_u4_fast(); calculated_attribute_length += code_attribute_length + sizeof(code_attribute_name_index) + sizeof(code_attribute_length); if (LoadLineNumberTables && _cp->symbol_at(code_attribute_name_index) == vmSymbols::tag_line_number_table()) { // ... } else if (LoadLocalVariableTables && _cp->symbol_at(code_attribute_name_index) == vmSymbols::tag_local_variable_table()) { // ... } else if (_major_version >= Verifier::STACKMAP_ATTRIBUTE_MAJOR_VERSION && _cp->symbol_at(code_attribute_name_index) == vmSymbols::tag_stack_map_table()) { // ... } else { // Skip unknown attributes cfs->skip_u1(code_attribute_length, CHECK_(nullHandle)); } } // end while }// end if ... } // end while
最终会将这些分析的信息存储到Method或ConstMethod对象中。所以会创建Method或ConstMethod对象,由于方法中的各个属性已经解析完成,所以也就能得出需要为Method或ConstMethod创建的内存的大小。
1、创建Method与ConstMethod对象
调用语句如下:
// All sizing information for a Method* is finally available, now create it InlineTableSizes sizes( total_lvt_length, linenumber_table_length, exception_table_length, checked_exceptions_length, method_parameters_length, generic_signature_index, runtime_visible_annotations_length + runtime_invisible_annotations_length, runtime_visible_parameter_annotations_length + runtime_invisible_parameter_annotations_length, runtime_visible_type_annotations_length + runtime_invisible_type_annotations_length, annotation_default_length, 0 ); Method* m = Method::allocate( _loader_data, code_length, access_flags, &sizes, ConstMethod::NORMAL, CHECK_(nullHandle));
其中的InlineTableSizes类中定义了保存方法中相关属性的大小的字段,定义如下:
class InlineTableSizes : StackObj { // 宏扩展后的结果如下: // int _localvariable_table_length; // int _compressed_linenumber_size; // int _exception_table_length; // int _checked_exceptions_length; // int _method_parameters_length; // int _generic_signature_index; // int _method_annotations_length; // int _parameter_annotations_length; // int _type_annotations_length; // int _default_annotations_length; // declarations INLINE_TABLES_DO(INLINE_TABLE_DECLARE) // ... }
之前已经介绍过ConstMethod类的内存布局结构,如下图所示。
可以看到,除了方法的各属性和字节码外,其它的大小都可以通过类中的相关字段保存起来。
调用Method::allocate()方法分配内存,Method::allocate()方法的实现如下:
Method* Method::allocate(ClassLoaderData* loader_data, int byte_code_size, AccessFlags access_flags, InlineTableSizes* sizes, ConstMethod::MethodType method_type, TRAPS) { assert(!access_flags.is_native() || byte_code_size == 0, "native methods should not contain byte codes"); ConstMethod* cm = ConstMethod::allocate(loader_data, byte_code_size, sizes, method_type, CHECK_NULL); int size = Method::size(access_flags.is_native()); return new (loader_data, size, false, MetaspaceObj::MethodType, THREAD) Method(cm, access_flags, size); } ConstMethod* ConstMethod::allocate(ClassLoaderData* loader_data, int byte_code_size, InlineTableSizes* sizes, MethodType method_type, TRAPS) { int size = ConstMethod::size(byte_code_size, sizes); return new (loader_data, size, true, MetaspaceObj::ConstMethodType, THREAD) ConstMethod( byte_code_size, sizes, method_type, size); }
需要关注对Method与ConstMethod分配内存的大小。调用Method::size()方法计算Method对象所需要分配的内存大小,如下:
int Method::size(bool is_native) { // If native, then include pointers for native_function and signature_handler int extra_bytes = (is_native) ? 2*sizeof(address*) : 0; int extra_words = align_size_up(extra_bytes, BytesPerWord) / BytesPerWord; return align_object_size(header_size() + extra_words); // 返回的是字大小 } static int header_size() { return sizeof(Method)/HeapWordSize; }
计算ConstMethod对象所需要分配的内存大小,除了ConstMethod类中表示方法的各属性占用的内存大小外,还需要传递字节码大小byte_code_size与其它属性sizes的大小,这样就可以调用ConstMethod::size()方法进行计算了,如下:
int ConstMethod::size(int code_size,InlineTableSizes* sizes) { int extra_bytes = code_size; if (sizes->compressed_linenumber_size() > 0) { extra_bytes += sizes->compressed_linenumber_size(); } if (sizes->checked_exceptions_length() > 0) { extra_bytes += sizeof(u2); extra_bytes += sizes->checked_exceptions_length() * sizeof(CheckedExceptionElement); } if (sizes->localvariable_table_length() > 0) { extra_bytes += sizeof(u2); extra_bytes += sizes->localvariable_table_length() * sizeof(LocalVariableTableElement); } if (sizes->exception_table_length() > 0) { extra_bytes += sizeof(u2); extra_bytes += sizes->exception_table_length() * sizeof(ExceptionTableElement); } if (sizes->generic_signature_index() != 0) { extra_bytes += sizeof(u2); } if (sizes->method_parameters_length() > 0) { extra_bytes += sizeof(u2); extra_bytes += sizes->method_parameters_length() * sizeof(MethodParametersElement); } // Align sizes up to a word. extra_bytes = align_size_up(extra_bytes, BytesPerWord); // One pointer per annotation array if (sizes->method_annotations_length() > 0) { extra_bytes += sizeof(AnnotationArray*); } if (sizes->parameter_annotations_length() > 0) { extra_bytes += sizeof(AnnotationArray*); } if (sizes->type_annotations_length() > 0) { extra_bytes += sizeof(AnnotationArray*); } if (sizes->default_annotations_length() > 0) { extra_bytes += sizeof(AnnotationArray*); } int extra_words = align_size_up(extra_bytes, BytesPerWord) / BytesPerWord; return align_object_size(header_size() + extra_words); // 内存大小的单位为字 } static int header_size() { return sizeof(ConstMethod)/HeapWordSize; }
调用header_size()方法获取ConstMethod本身占用的内存大小,然后加上extra_words就是需要开辟的内存大小,单位为字。
2、为Method设置属性
调用Method::allocate()方法分配Method对象与ConstMethod对象后,会在parse_method()方法中设置相关的属性,代码如下:
// Fill in information from fixed part (access_flags already set) m->set_constants(_cp); m->set_name_index(name_index); m->set_signature_index(signature_index); if (args_size >= 0) { m->set_size_of_parameters(args_size); } else { m->compute_size_of_parameters(THREAD); } // Fill in code attribute information m->set_max_stack(max_stack); m->set_max_locals(max_locals); // Copy byte codes m->set_code(code_start);
调用m->set_code()方法的实现如下:
void set_code(address code) { return constMethod()->set_code(code); } void set_code(address code) { if (code_size() > 0) { memcpy(code_base(), code, code_size()); } } address code_base() const { return (address) (this+1); // 存储在ConstMethod本身占用的内存之后 }
当字节码的大小不为0时,调用memcpy()方法将字节码内容拷贝到紧挨着ConstMethod本身占用的内存之后。
另外还会填充ConstMethod中的相关属性,因为之前已经开辟好了存储空间,所以根据解析的结果得到相关的值后也会做填充,这一部分繁琐但没有特别的逻辑,这里不介绍。
相关文章的链接如下:
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)
作者持续维护的个人博客classloading.com。
关注公众号,有HotSpot源码剖析系列文章!