专注虚拟机与编译器研究

第60篇-获取编译任务

在前面介绍过OSR或方法编译,如果已经决定要编译,都会调用SimpleThresholdPolicy::compile()函数进行编译。调用的相关函数如下:

 

 

 

1、提交编译任务 

在SimpleThresholdPolicy::compile()函数中会调用SimpleThresholdPolicy::submit_compile()函数提交编译任务,此函数的实现如下:

void SimpleThresholdPolicy::submit_compile(
 methodHandle mh,
 int bci,
 CompLevel level,
 JavaThread* thread
) {
  int hot_count = (bci == InvocationEntryBci) ? mh->invocation_count() : mh->backedge_count();
  CompileBroker::compile_method(mh, bci, level, mh, hot_count, "tiered", thread);
}

传入的参数level通常就是目标编译层级,这是通过前一篇文章介绍的、调用AdvancedThresholdPolicy::common()函数计算出来的。

如果是OSR编译,则参考回边计数,如果是方法编译,则参考方法调用计数。最终会调用CompileBroker::compile_method()函数。

nmethod* CompileBroker::compile_method(
 methodHandle   method,
 int            osr_bci,
 int            comp_level,
 methodHandle   hot_method,
 int            hot_count,
 const char*   comment,
 Thread*        THREAD
) {

  AbstractCompiler *comp = CompileBroker::compiler(comp_level);
  // ...

  if (osr_bci == InvocationEntryBci) { // 方法编译
    nmethod* method_code = method->code();
    if (method_code != NULL) {
      if (compilation_is_complete(method, osr_bci, comp_level)) {
        return method_code;
      }
    }
    if (method->is_not_compilable(comp_level)) {
      return NULL;
    }
  } else { // 栈上替换
    nmethod* nm = method->lookup_osr_nmethod_for(osr_bci, comp_level, false);
    if (nm != NULL)
    	return nm;
    if (method->is_not_osr_compilable(comp_level))
    	return NULL;
  }

  // 如果是本地方法,且不是intrinsic相关方法,则调用NativeLookup加载对应的本地代码
  if (method->is_native() && !method->is_method_handle_intrinsic()) {
    bool in_base_library;
    address adr = NativeLookup::lookup(method, in_base_library, THREAD);
  }

  if (method->is_native()) {
     // PreferInterpreterNativeStubs表示是否总是使用解释器的stub调用本地方法,默认值为false
    if (!PreferInterpreterNativeStubs || method->is_method_handle_intrinsic()) {
      int compile_id;
      {
        MutexLocker locker(MethodCompileQueue_lock, THREAD);
        compile_id = assign_compile_id(method, standard_entry_bci);
      }
      // 注意这里调用的create_native_wrapper()函数为本地方法生成编译执行的入口,这在
      // 之前详细介绍过
      char* xx =  method->name()->as_C_string();
      (void) AdapterHandlerLibrary::create_native_wrapper(method, compile_id);
    } else {
      return NULL;
    }
  } else {
    // 如果因为存储代码的CodeCache已经满了,则直接返回NULL,这样就不会阻塞方法的继续
    // 执行
    if (!should_compile_new_jobs()) {
       CompilationPolicy::policy()->delay_compilation(method());
       return NULL;
    }
    compile_method_base(method, osr_bci, comp_level, hot_method, hot_count, comment, THREAD);
  }

  if(osr_bci  == InvocationEntryBci){ // 方法编译
   return  method->code() ;
  }else{ // OSR编译
   return  method->lookup_osr_nmethod_for(osr_bci, comp_level, false);
  }
}  

调用判断使用C1还是C2编译器进行方法编译,C1与C2各自有实现AbstractCompiler接口,分别是C1的Compiler与C2的C2Compiler。函数的实现如下:

static AbstractCompiler* compiler(int comp_level) {
    if (is_c2_compile(comp_level)){
    	return _compilers[1]; // C2
    }
    if (is_c1_compile(comp_level)){
    	return _compilers[0]; // C1
    }
    return NULL;
}

其中的_compilers的初始化在之前介绍过,存储的分别是C1的Compiler与C2的C2Compiler。  

如果是本地方法,那么需要调用AdapterHandlerLibrary::create_native_wrapper()函数生成编译执行的入口例程,这在之前详细介绍过。

如果是非本地方法,调用compile_method_base()函数提交编译任务,此函数的实现如下:

void CompileBroker::compile_method_base(
 methodHandle  method,
 int           osr_bci,
 int           comp_level,
 methodHandle  hot_method,
 int           hot_count,
 const char*   comment,
 Thread*       thread
) { 

  // 判断是否有必要编译此次编译,当能获取到此次编译的一个适合的编译结果时
  // 直接返回即可
  if (compilation_is_complete(method, osr_bci, comp_level)) {
    return;
  }

  // 如果此次编译任务已经在编译队列中存在,则直接返回
  if (compilation_is_in_queue(method, osr_bci)) {
    return;
  }

  // ...

  
  CompileTask*   task     = NULL;
  bool           blocking = false;
  // 查一下此次编译任务对应的编译器使用的编译队列,C1使用的是CompileBroker::_c2_method_queue,
  // C2使用的是CompileBroker::_c1_method_queue
  CompileQueue* queue  = compile_queue(comp_level);

  
  { // 匿名块开始    
    MutexLocker locker(queue->lock(), thread);

    // 当编译任务已经在编译队列中时,直接返回
    if (compilation_is_in_queue(method, osr_bci)) {
      return;
    }

    // 由于之前可能已经完成编译,直接返回
    if (compilation_is_complete(method, osr_bci, comp_level)) {
      return;
    }

    uint compile_id = assign_compile_id(method, osr_bci);
    if (compile_id == 0) {
      return;
    }

    // 判断线程是否需要等编译任务完成后才继续执行,会判断BackgroundCompilation选项的值,默认为true,
    // 所以通常是后台编译,不需要等待
    // 当该方法有断点的时候并不进行编译,当参数-XX:-BackgroundCompilation设置成不是后台编译的时候,
    // 并不代表是在用户线程编译,而是提交任务CompileTask到CompileQueue,唯一的区别是堵塞当前线程等待
    // CompileThread直到Task编译成功
    blocking = is_compile_blocking(method, osr_bci);
    // 创建编译任务
    task = create_compile_task(
		   queue,
		   compile_id,
		   method,
		   osr_bci,
		   comp_level,
		   hot_method,
		   hot_count,
		   comment,
		   blocking
	 );
  } // 匿名块结束

  // 如果blocking为true,表示需要等待编译任务完成后才能继续执行方法,如OSR编译
  if (blocking) {
    wait_for_completion(task);
  }
}

调用的create_compile_task()函数创建编译任务,函数的实现如下:

CompileTask* CompileBroker::create_compile_task(
  CompileQueue* queue,
  int           compile_id,
  methodHandle  method,
  int           osr_bci,
  int           comp_level,
  methodHandle  hot_method,
  int           hot_count,
  const char*   comment,
  bool          blocking
) {
  CompileTask* new_task = allocate_task();
  new_task->initialize(compile_id, method, osr_bci, comp_level,hot_method, hot_count, comment,blocking);
  queue->add(new_task);
  return new_task;
}

创建CompileTask任务,然后添加到C1或C2的任务队列中,此函数就直接返回了。这个任务会被C1编译器线程或C2编译器线程从队列中获取,然后编译。至于当前提交任务的函数是否需要等待编译任务完成,这取决与BackgroundCompilation选项的值,默认为true,表示异步编译,所以不会等待编译完成。

2、从CompileQueue队列中取任务进行编译

调用栈如下:

Threads::create_vm()  thread.cpp	
CompileBroker::compilation_init()  compileBroker.cpp	

在创建虚拟机实例时会调用compilation_init()函数初始化CompileBroker,compilation_init()函数负责初始化编译相关组件,包括编译器实现,编译线程,计数器等。其中还会调用CompileBroker::make_compiler_thread()函数,在这个函数中创建出对应的编译线程并启动,启动后的新线程线程会调用compiler_thread_loop()函数从CompileQueue中取任务进行编译。启动的新的编译线程的调用栈如下: 

clone()                                clone.S
start_thread()                         pthread_create.c
java_start()                           os_linux.cpp
JavaThread::run()                      thread.cpp
JavaThread::thread_main_inner()        thread.cpp
compiler_thread_entry()                thread.cpp
CompileBroker::compiler_thread_loop()  compileBroker.cpp

编译线程会从编译任务队列中不断获取编译任务然后编译,compiler_thread_loop()函数的实现如下:

void CompileBroker::compiler_thread_loop() {
  CompilerThread* thread = CompilerThread::current();
  CompileQueue* queue = thread->queue();

  // 只有编译器初始化时因为CodeCache内存不足导致初始化失败这一种情形,编译会被禁用
  // 如果没有禁用编译,初始化正常,is_compilation_disabled_forever()函数永远返回false
  while (!is_compilation_disabled_forever()) {
    HandleMark hm(thread);    

   // 从编译队列中获取一个新的任务,如果没有新的编译任务,则get()方法等待5s
    CompileTask* task = queue->get();
    if (task == NULL) {
      continue;
    }

    CompileTaskWrapper ctw(task);
    nmethodLocker result_handle;  
    task->set_code_handle(&result_handle);
    methodHandle method(thread, task->method());

    // 只有在没有断点的情况下才会进行编译
    if (method()->number_of_breakpoints() == 0) {
      // Compile the method.
      if ((UseCompiler || AlwaysCompileLoopMethods) && 
                  CompileBroker::should_compile_new_jobs()) {
        ...
        invoke_compiler_on_method(task); // 对编译任务进行编译
      } else {
        // 如果编译已经被禁止,则移除队列中的编译任务
        method->clear_queued_for_compilation();
      }
    }
  }

  shutdown_compiler_runtime(thread->compiler(), thread);
}

编译线程启动后自动执行如上函数,循环的从编译任务队列中取出编译任务执行编译,在获取到新的编译任务后还会做一些判断,如方法有断点的时候并不进行编译。方

下面看invoke_compiler_on_method()函数的实现,如下:

void CompileBroker::invoke_compiler_on_method(CompileTask* task) {
  // ...
  int osr_bci = task->osr_bci(); 
  bool is_osr = (osr_bci != standard_entry_bci);
  int task_level = task->comp_level();

  { 
      ciEnv ci_env(task, system_dictionary_modification_counter);

      // 传入Method*后返回ciMethod*,在编译中会操作ciMethod*
      ciMethod* target = ci_env.get_method_from_handle(target_handle);

      AbstractCompiler *comp = compiler(task_level);
      comp->compile_method(&ci_env, target, osr_bci);
   }
  // ...
}

注意无论是OSR还是方法编译,最终都会调用Compiler::compile_method()进行编译。

这里还需要关注的是ciEnv、ciMethod等以ci开头的一系列类,ci是compiler interface的缩写,由于HotSpot VM中会有各种编译器,所以专门定义了编译器相关接口,当我们需要编译方法时,会将相关的数据封装为ci开头的一些类实例。如调用get_method_from_handle()方法将Method*封装为ciMethod。

其中调用的compiler()函数的实现如下:

static AbstractCompiler* compiler(int comp_level) {
    if (is_c2_compile(comp_level))
    	return _compilers[1]; // C2编译器C2Compiler
    if (is_c1_compile(comp_level))
    	return _compilers[0]; // C1编译器compiler
    return NULL;
}

对于C1来说,会调用Compiler的compile_method()函数;对于C2来说,会调用C2Compiler的compile_method()函数。这里我们只看C1的编译,调用的Compilation::compile_method()函数的重要实现如下:

// 编译方法或OSR
int frame_size = compile_java_method();
// ...

if (InstallMethods) { // InstallMethods选项的默认值为true
  // 安装方法或OSR
  install_code(frame_size);
}

调用compile_java_method()函数编译,这个方法是C1编译器的入口方法,后面在介绍C1编译器时会从这个函数的实现开始介绍。编译完成后调用install_code()函数“安装”代码。下一篇将详细介绍OSR和方法编译后安装与卸载代码的过程。

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

 

 

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

导航