原创

方法解析之Method与ConstMethod介绍

HotSpot通过Method与ConstMethod来保存方法元信息。

1、Method

Method没有子类,定义在method.hpp文件中,其类继承关系如下图:

file

Method用于表示一个Java方法,因为一个应用有成千上万个方法,因此保证Method类在内存中短小非常有必要。为了本地GC方便,Method把所有的指针变量和方法大小放在Method内存布局的前面。

方法本身的不可变数据,如字节码用ConstMethod表示,可变数据如Profile统计的性能数据等用MethodData表示,都通过指针访问。

如果是本地方法,Method内存结构的最后是native_function和signature_handler属性,按照解释器的要求,这两个必须在固定的偏移处。

Method类中声明的属性如下:

源代码位置:/vm/oops/method.hpp

class Method : public Metadata {
 friend class VMStructs;
 private:
  ConstMethod*      _constMethod;           // Method read-only data.
  // MethodData结构基础是ProfileData,记录函数运行状态下的数据
  // MethodData里面分为3个部分,一个是函数类型等运行相关统计数据,一个是参数类型运行相关统计数据,
  // 还有一个是extra扩展区保存着deoptimization的相关信息
  MethodData*       _method_data;
  MethodCounters*   _method_counters;
  AccessFlags       _access_flags;          // Access flags
  // vtable index of this method (see VtableIndexFlag)
  // note: can have vtables with >2**16 elements (because of inheritance)
  int               _vtable_index;

  u2                _method_size;           // size of this object
  u1                _intrinsic_id;          // vmSymbols::intrinsic_id (0 == _none)
  // Flags,在定义时指定各个变量占用的位
  u1                _jfr_towrite      : 1,  // Flags
                    _caller_sensitive : 1,
                    _force_inline     : 1,
                    _hidden           : 1,
                    _dont_inline      : 1,
                                      : 3;

  // Entry point for calling both from and to the interpreter.
  address _i2i_entry;           // All-args-on-stack calling convention

  /*
    _adapter 指向该Java方法的签名(signature)所对应的 i2c2i adapter stub。其实是一个 i2c stub
    和一个 c2i stub 粘在一起这样的对象,可以看到用的时候都是从 _adapter 取 get_i2c_entry() 或
    get_c2i_entry()。这些adapter stub用于在HotSpot VM里的解释模式与编译模式的代码之间适配其
    calling convention。HotSpot VM里的解释模式calling convention用栈来传递参数,而编译模式的
    calling convention更多采用寄存器来传递参数,两者不兼容,因而从解释模式的代码调用已经被编译的方法,
    或者反之,都需要在调用时进行适配。
  */
  // Adapter blob (i2c/c2i) for this Method*. Set once when method is linked.
  AdapterHandlerEntry*   _adapter;

  /*
    _from_compiled_entry 初始值指向c2i adapter stub。原因上面已经说了,因为一开始该方法尚未被JIT编译,
    需要在解释模式执行,那么从已经JIT编译好的Java方法调用过来的话就需要进行calling convention的转换,
    把参数挪到正确的位置上。当该方法被JIT编译并“安装”完之后,_from_compiled_entry 就会指向编译出来的机
    器码的入口,具体说时指向verified entry point。如果要抛弃之前编译好的机器码,那么 _from_compiled_entry
    会恢复为指向 c2i stub。
  */
  // Entry point for calling from compiled code, to compiled code if it exists
  // or else the interpreter.
  // Cache of: _code ? _code->entry_point() : _adapter->c2i_entry()
  volatile address _from_compiled_entry;

  // The entry point for calling both from and to compiled code is
  // "_code->entry_point()".  Because of tiered compilation and de-opt, this
  // field can come and go.  It can transition from NULL to not-null at any
  // time (whenever a compile completes).  It can transition from not-null to
  // NULL only at safepoints (because of a de-opt).
  // nmethod全名native method,指向的是Java method编译的一个版本。
  // 当一个方法被JIT编译后会生成一个nmethod,指向的是编译的代码
  // _code的类型为nmethod
  nmethod* volatile  _code;  // Points to the corresponding piece of native code

  /*
    _from_interpreted_entry 初始的值与 _i2i_entry 一样。但后面当该Java方法被JIT编译并“安装”之后,
    _from_interpreted_entry 就会被设置为指向 i2c adapter stub。而如果因为某些原因需要抛弃掉之前已
    经编译并安装好的机器码,则 _from_interpreted_entry 会被恢复为 _i2i_entry。
   */
  // Cache of _code ? _adapter->i2c_entry() : _i2i_entry
  volatile address  _from_interpreted_entry; // 如果有_code,则通过i2c_entry转向编译方法,否则通过_i2i_entry转向解释方法

  ...
}

HotSpot虚拟机中的Method存储在元数据区(在JDK1.8之前是PermGen)。

Method中最后定义的几个属性非常重要。用以方法的解释执行和编译执行。一个方法可能有多个入口:

1、_i2i_entry :指向字节码解释执行的入口;

2、_code->entry_point() :指向JIT编译代码的入口。编译后的代码存储在CodeCache中,这是专门为动态生成的代码开辟的一块本地内存;

3、i2c和c2i适配器:用来在解释执行和编译执行之间进行转换,由于解释执行和编译执行的调用约定不同,所以专门做了适配器来适配。

可以这样总结一下,如果转换的目标是解析执行,那么:

  • from_compiled_code_entry_point 的值为 c2i adapter
  • from_interpreter_entry_point 的值为 interpreter entry point
    如果转换的目标是编译执行:

  • from_compiled_code_entry_point 的值为 nmethod entry point

  • from_interpreter_entry_point 的值为 i2c adapter
    Method类中定义的各属性的介绍如下表所示。
    file

基中_access_flags的取值如下:
file

_vtable_index的取值如下:
file

2、ConstMethod类介绍

ConstMethod对象代表方法中不可变的部分,例如字节码。类的定义如下:

源代码位置:/hotspot/src/share/vm/oop

class ConstMethod : public MetaspaceObj {
  ...
private:
  ...

  //  其中_constants,_method_idnum这两个是连接Method的参数,因为Method有ConstMethod指针,
  //  但ConstMethod没有Method的指针,需要通过如下步骤来查找:
  //  ConstantPool -> InstanceKlass -> Method数组->通过_method_idnum获取对应的Method指针
  //  原文链接:https://blog.csdn.net/raintungli/article/details/83857136
  ConstantPool*     _constants;        // Constant pool

  int               _constMethod_size;
  u2                _flags;

  // Size of Java bytecodes allocated immediately after Method*.
  u2                _code_size;
  u2                _name_index;       // Method name (index in constant pool)
  u2                _signature_index;  // Method signature (index in constant pool)
  u2                _method_idnum;     // unique identification number for the method within the class
                                       // initially corresponds to the index into the methods array.
                                       // but this may change with redefinition
  u2                _max_stack;        // Maximum number of entries on the expression stack
  u2                _max_locals;       // Number of local variables used by this method
  u2                _size_of_parameters;// size of the parameter block (receiver + arguments) in words
  ...
}

该类用于表示方法的不可变部分,如方法的id(_method_idnum),方法的字节码大小,方法名在常量池中的索引等,其中_constMethod_size的大小以字宽为单位,_code_size的大小以字节为单位,其内存结构如下图,因为异常检查表平均长度小于2,本地变量表大多数情况下没有,所以这两个没有被压缩保存。访问这些内嵌表都很快,不是性能瓶颈。ConstMethod提供了获取内嵌部分,如字节码的起始地址,然后可以据此偏移地址获取的方法字节码。

file

正文到此结束