专注虚拟机与编译器研究

第6.2篇-方法解析

在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中的相关属性,因为之前已经开辟好了存储空间,所以根据解析的结果得到相关的值后也会做填充,这一部分繁琐但没有特别的逻辑,这里不介绍。 

 

 

 

posted on 2020-08-08 09:35  鸠摩(马智)  阅读(577)  评论(0编辑  收藏  举报

导航