原创

方法的连接

方法连接做的最主要的事就是为方法的执行设置解释入口和编译入口。在InstanceKlass::link_class_impl()方法中对字节码验证完成后会调用InstanceKlass::link_methods()方法,如下:

// Now relocate and link method entry points after class is rewritten.
// This is outside is_rewritten flag. In case of an exception, it can be
// executed more than once.
void InstanceKlass::link_methods(TRAPS) {
  int len = methods()->length();
  for (int i = len-1; i >= 0; i--) {
    methodHandle m(THREAD, methods()->at(i));

    // Set up method entry points for compiler and interpreter    .
    m->link_method(m, CHECK);
  }
}

调用的link_method()方法的实现如下:

// Called when the method_holder is getting linked. Setup entrypoints so the method
// is ready to be called from interpreter, compiler, and vtables.
void Method::link_method(methodHandle h_method, TRAPS) {
  // If the code cache is full, we may reenter this function for the
  // leftover methods that weren't linked.
  if (_i2i_entry != NULL){
      return;
  }
  assert(_adapter == NULL, "init'd to NULL" );
  assert( _code == NULL, "nothing compiled yet" );

  // Setup interpreter entrypoint
  assert(this == h_method(), "wrong h_method()" );
  address entry = Interpreter::entry_for_method(h_method);
  assert(entry != NULL, "interpreter entry must be non-null");
  // Sets both _i2i_entry and _from_interpreted_entry
  set_interpreter_entry(entry);

  // ...

  // Setup compiler entrypoint.  This is made eagerly, so we do not need
  // special handling of vtables.  An alternative is to make adapters more
  // lazily by calling make_adapter() from from_compiled_entry() for the
  // normal calls.  For vtable calls life gets more complicated.  When a
  // call-site goes mega-morphic we need adapters in all methods which can be
  // called from the vtable.  We need adapters on such methods that get loaded
  // later.  Ditto 同样,也一样; for mega-morphic itable calls.  If this proves to be a
  // problem we'll make these lazily later.
  (void) make_adapters(h_method, CHECK);

  // ONLY USE the h_method now as make_adapter may have blocked

}

调用的Interpreter::entry_for_method()方法非常重要,此方法会根据方法类型获取对应的方法执行例程(就是一段机器代码,为方法的调用准备栈帧等必要信息)的入口地址,这在后面讲解解释执行时会详细介绍。这里只简单介绍一下。Interpreter::entry_for_method()方法的实现如下:

static address entry_for_method(methodHandle m) {
      AbstractInterpreter::MethodKind mk = method_kind(m);
      return entry_for_kind(mk);
}

static address entry_for_kind(MethodKind k) {
      assert(0 <= k && k < number_of_method_entries, "illegal kind");
      return _entry_table[k];
}

MethodKind是枚举类型,定义了表示不同方法类型的常量,比如普通的非同步方法、普通的同步方法、本地非同步方法、本地同步方法,在调用这些方法时,调用约定或栈帧结构等不同,所以对应的例程入口也不同,通过entry_for_kind()方法直接获取入口地址即可。_entry_table在HotSpot启动时就会初始化,其中存储的就是不同类型方法的例程入口,直接获取即可。需要说明的是,这些例程入口是解释执行时的入口。

获取到执行入口地址后调用set_interpreter_entry()方法进行保存,方法的实现如下:

// Only used when first initialize so we can set _i2i_entry and _from_interpreted_entry
void set_interpreter_entry(address entry){
      _i2i_entry = entry;
      _from_interpreted_entry = entry;
}

通过Method方法中的_i2i_entry与_from_interpreted_entry属性来保存解释执行的入口地址。 

在 Method::link_method()方法中调用make_adapters()方法设置编译执行的入口地址,方法的实现如下:

address Method::make_adapters(methodHandle mh, TRAPS) {
  // Adapters for compiled code are made eagerly here.  They are fairly
  // small (generally < 100 bytes) and quick to make (and cached and shared)
  // so making them eagerly shouldn't be too expensive.
  AdapterHandlerEntry* adapter = AdapterHandlerLibrary::get_adapter(mh);
  if (adapter == NULL ) {
     THROW_MSG_NULL(vmSymbols::java_lang_VirtualMachineError(), "out of space in CodeCache for adapters");
  }

  mh->set_adapter_entry(adapter);
  mh->_from_compiled_entry = adapter->get_c2i_entry();
  return adapter->get_c2i_entry();
}

方法获取适配器adapter后通过Method的_adapter属性来保存,这个_adapter是用来适配从解释执行转换为编译执行或从编译执行转换为解释执行的。_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更多采用寄存器来传递参数,两者不兼容,因而从解释模式的代码调用已经被编译的方法,或者反之,都需要在调用时进行适配。

这里不太明白的话不要紧,这部分知识是方法执行的重点,后面会详细介绍。

正文到此结束