在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源码剖析系列文章!