专注虚拟机与编译器研究

第61篇-安装与卸载编译方法和OSR

如果方法或OSR完成编译后,还需要安装到对应的位置才能执行。 

可以看一下Compilation::compile_method()函数,最重要的实现代码如下:

// compile method
int frame_size = compile_java_method();
// ...

if (InstallMethods) { // InstallMethods选项默认的值为true
  install_code(frame_size);
}

调用compile_java_method()函数编译,这个函数是C1编译器或C2编译器的入口方法,后面在介绍C1编译器时会从这个函数的实现开始介绍。编译完成后调用install_code()函数“安装”代码。install_code()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/c1/c1_Compilation.cpp

void Compilation::install_code(int frame_size) {
  _env->register_method(
    method(),
    osr_bci(),
    &_offsets,
    in_bytes(_frame_map->sp_offset_for_orig_pc()),
    code(),
    in_bytes(frame_map()->framesize_in_bytes()) / sizeof(intptr_t),
    debug_info_recorder()->_oopmaps,
    exception_handler_table(),
    implicit_exception_table(),
    compiler(),
    _env->comp_level(),
    has_unsafe_access(),
    SharedRuntime::is_wide_vector(max_vector_size())
  );
}

其中_env变量在Compilation类中的定义如下:

ciEnv*             _env;

也就是调用ciEnv中的register_method()函数注册编译的方法(热点代码块也是以方法为单位编译的)。ciEnv相当于执行编译任务的一个中间类,充当编译器、后台编译线程和HotSpot VM运行时环境之间的桥梁。

调用的register_method()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/ci/ciEnv.cpp
void ciEnv::register_method(
 ciMethod*    target,
 int          entry_bci,
 CodeOffsets* offsets,
 int          orig_pc_offset,
 CodeBuffer*  code_buffer,
 int          frame_words,
 OopMapSet*   oop_map_set,
 ExceptionHandlerTable*  handler_table,
 ImplicitExceptionTable* inc_table,
 AbstractCompiler*       compiler,
 int     comp_level,
 bool    has_unsafe_access,
 bool    has_wide_vectors
) {

  nmethod* nm = NULL;
  {
    // ...    
    methodHandle method(THREAD, target->get_Method());
    // ...
    // 创建一个nmethod实例来表示编译后的方法
    nm =  nmethod::new_nmethod(method,
                               compile_id(),
                               entry_bci,
                               offsets,
                               orig_pc_offset,
                               debug_info(), dependencies(), code_buffer,
                               frame_words, oop_map_set,
                               handler_table, inc_table,
                               compiler, comp_level);

    // 因为创建nmethod时会将code_buffer中的内容复制到nmethod中,所以这里需要释放CodeBuffer
    code_buffer->free_blob();

    if (nm != NULL) {
      if (task() != NULL) {
        task()->set_code(nm);
      }

      //如果是非栈上替换 
      if (entry_bci == InvocationEntryBci) {
        if (TieredCompilation) {
          // 如果有之前安装的版本,设置为不可用
          nmethod* old = method->code();
          if (old != NULL) {
            old->make_not_entrant();
          }
        }
        // nmethod安装到Method上,安装完成可以立即执行
        method->set_code(method, nm);
      } else { // 如果是栈上替换
        // 将其添加到Klass的osr_nmethods链表上
        method->method_holder()->add_osr_nmethod(nm);
      }
    } 
    
  }  
}    

可以看到,会将编译完成后的方法设置到Method::_code属性上,调用add_osr_nmethod()函数将栈上替换的方法添加到_osr_nmethods_head链表上。

在HotSpot VM的实现里,一般一个类被加载进来之后,里面的方法最早也要到它即将第一次被执行的时候才有可能被JIT编译器所编译。在那之前,非abstract非native的Java方法都只是以Java字节码的形式存在,没有对应的机器码(上面所说的 Method::_code属性的值为NULL)

如果在调用方法时,会检测Method::_code属性,如果不为空,则执行编译完成后的方法;对于栈上替换来说,会通过之前介绍的lookup_osr_nmethod_for()函数查找到本次编译的结果,然后执行栈上替换操作。

(1)安装与卸载编译方法

Method::set_code()函数的实现如下:

void Method::set_code(methodHandle mh, nmethod *code) {

  mh->_code = code;   

  int comp_level = code->comp_level();
  if (comp_level > mh->highest_comp_level()) {
    mh->set_highest_comp_level(comp_level);
  }

  OrderAccess::storestore();
  mh->_from_compiled_entry = code->verified_entry_point();
  OrderAccess::storestore();

  // 如果不是MethodHandle的invoke等方法,即编译代码可以立即执行
  if (!mh->is_method_handle_intrinsic()){
     mh->_from_interpreted_entry = mh->get_i2c_entry();
  }
}

所谓“安装”其实就是把nmethod与其对应的Method关联起来,把各入口都设置上。设置Method::_code、Method::_from_compiled_entry和Method::_from_interpreted_entry属性。这3个属性对于编译执行与解释执行非常重要,在后面会详细介绍。

与“安装”相反的“卸载”逻辑就是抛弃已编译好的机器码,调用的函数如下:

// 清除编译生成的机器码,返回解释执行
void Method::clear_code() {
  // _adapter是定义在Method类中的、类型为AdapterHandlerEntry*的变量;
  if (_adapter == NULL) {
    _from_compiled_entry    = NULL;
  } else {
    // 没有编译的版本时,只能回到解释执行
    _from_compiled_entry    = _adapter->get_c2i_entry();
  }

  OrderAccess::storestore();
  _from_interpreted_entry = _i2i_entry;
  OrderAccess::storestore();

  _code = NULL;
}

当nmethod被标记成not_entrant或者zombie时,或者执行CodeCache垃圾回收时会调用如上函数卸载编译的方法。

(2)安装与卸载OSR

调用InstanceKlass::add_osr_nmethod()与InstanceKlass::remove_osr_nmethod()函数对OSR进行安装与卸载。

add_osr_nmethod()函数用于将需要执行栈上替换的nmethod实例插入到InstanceKlass的osr_nmethods链表上,函数的实现如下:

void InstanceKlass::add_osr_nmethod(nmethod* n) {
  // 获取锁
  OsrList_lock->lock_without_safepoint_check();
  //校验必须是栈上替换方法
  assert(n->is_osr_method(), "wrong kind of nmethod");
  // 将_osr_nmethods_head设置成n的下一个方法
  n->set_osr_link(osr_nmethods_head());
  // 将n设置为_osr_nmethods_head
  set_osr_nmethods_head(n);
  // 如果使用分层编译
  if (TieredCompilation) {
    Method* m = n->method();
    // 更新最高编译级别
    m->set_highest_osr_comp_level(MAX2(m->highest_osr_comp_level(), n->comp_level()));
  }
  // 解锁
  OsrList_lock->unlock();
 
  if (TieredCompilation) {
    // 查找所有低于nmethod的编译级别的属于同一方法的nmethod实例,将其从osr_nmethods链表上移除
    for (int l = CompLevel_limited_profile; l < n->comp_level(); l++) {
      nmethod *inv = lookup_osr_nmethod(n->method(), n->osr_entry_bci(), l, true);
      if (inv != NULL && inv->is_in_use()) {
          inv->make_not_entrant();
      }
    }
  }
}
 
nmethod* osr_nmethods_head() const { return _osr_nmethods_head; };

与add_osr_nmethod()函数相对应的就是remove_osr_nmethod()函数,用于从osr_nmethod链表上移除nmethod,当nmethod被标记成not_entrant或者zombie时,或者执行CodeCache垃圾回收时会调用该函数,函数的实现如下:

void InstanceKlass::remove_osr_nmethod(nmethod* n) {

  // 校验是否栈上替换方法
  assert(n->is_osr_method(), "wrong kind of nmethod");
  nmethod* last = NULL;
  // 获取osr_nmethods链表的头元素
  nmethod* cur  = osr_nmethods_head();
  int max_level = CompLevel_none;  // Find the max comp level excluding n
  Method* m = n->method();
  // 遍历osr_nmethods链表直到遇到n,找到n所属的方法的所有nmehtod的最高编译级别
  while(cur != NULL && cur != n) {
    if (TieredCompilation && m == cur->method()) {
      // Find max level before n
      max_level = MAX2(max_level, cur->comp_level());
    }
    last = cur;
    cur = cur->osr_link();
  }
  nmethod* next = NULL;
  // 如果从链表中找到了目标nmethod
  if (cur == n) {
    // 将目标nmethod从链表中移除
    next = cur->osr_link();
    if (last == NULL) {
      set_osr_nmethods_head(next);
    } else {
      last->set_osr_link(next);
    }
  }
  n->set_osr_link(NULL);
  if (TieredCompilation) {
    cur = next;
    // 遍历链表,更新最大编译级别
    while (cur != NULL) {
      // Find max level after n
      if (m == cur->method()) {
        max_level = MAX2(max_level, cur->comp_level());
      }
      cur = cur->osr_link();
    }
    m->set_highest_osr_comp_level(max_level);
  }

}

 公众号 深入剖析Java虚拟机HotSpot 已经更新虚拟机源代码剖析相关文章到60+,欢迎关注,如果有任何问题,可加作者微信mazhimazh,拉你入虚拟机群交流

posted on 2022-02-10 09:01  鸠摩(马智)  阅读(348)  评论(0编辑  收藏  举报

导航