在前面介绍过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,拉你入虚拟机群交流