OSR or method compilation was described earlier. If you have decided to compile, you will call the SimpleThresholdPolicy::compile() function to compile. The relevant functions called are as follows:
1. Submit compilation task
Simplethresholdpolicy:: Submit is called in the SimpleThresholdPolicy::compile() function_ The compile() function submits the compilation task. The implementation of this function is as follows:
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); }
The passed in parameter level is usually the target compilation level, which is calculated by calling the AdvancedThresholdPolicy::common() function introduced in the previous article.
If it is OSR compilation, refer to the back edge count; if it is method compilation, refer to the method call count. Finally, compilebroker:: compile will be called_ Method() function.
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) { // Method compilation 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 { // Replace on stack 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; } // If it is a local method and not an intrinsic related method, call NativeLookup to load the corresponding local code 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()) { // Prefererpreternativestubs indicates whether local methods are always called using the stub of the interpreter. The default value is 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); } // Notice the create called here_ native_ The wrapper () function generates an entry for compilation execution for the local method, which is // I explained it in detail before char* xx = method->name()->as_C_string(); (void) AdapterHandlerLibrary::create_native_wrapper(method, compile_id); } else { return NULL; } } else { // If the CodeCache in which the code is stored is full, NULL will be returned directly, so that the continuation of the method will not be blocked // implement 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){ // Method compilation return method->code() ; }else{ // OSR compilation return method->lookup_osr_nmethod_for(osr_bci, comp_level, false); } }
Call to determine whether C1 or C2 Compiler is used for method compilation. C1 and C2 have AbstractCompiler interfaces, which are C1 Compiler and C2 Compiler respectively. The function is implemented as follows:
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; }
Among them_ The initialization of compilers was introduced earlier. The stored compilers are C1 and C2 respectively.
If it is a local method, you need to call adapterhandlerlibrary:: create_ native_ The wrapper () function generates an entry routine for compilation execution, which was described in detail earlier.
If it is a non local method, call compile_ method_ The base() function submits the compilation task. The implementation of this function is as follows:
void CompileBroker::compile_method_base( methodHandle method, int osr_bci, int comp_level, methodHandle hot_method, int hot_count, const char* comment, Thread* thread ) { // Judge whether it is necessary to compile this compilation. When a suitable compilation result of this compilation can be obtained // Just return directly if (compilation_is_complete(method, osr_bci, comp_level)) { return; } // If the compilation task already exists in the compilation queue, it will be returned directly if (compilation_is_in_queue(method, osr_bci)) { return; } // ... CompileTask* task = NULL; bool blocking = false; // Check the compilation queue used by the compiler corresponding to this compilation task. C1 uses compilebroker:_ c2_ method_ queue, // C2 uses compilebroker:_ c1_ method_ queue CompileQueue* queue = compile_queue(comp_level); { // Anonymous block start MutexLocker locker(queue->lock(), thread); // When the compilation task is already in the compilation queue, it returns directly if (compilation_is_in_queue(method, osr_bci)) { return; } // Since the compilation may have been completed before, return directly if (compilation_is_complete(method, osr_bci, comp_level)) { return; } uint compile_id = assign_compile_id(method, osr_bci); if (compile_id == 0) { return; } // Judge whether the thread needs to wait until the compilation task is completed before continuing. It will judge the value of the BackgroundCompilation option, which is true by default, // Therefore, it is usually compiled in the background without waiting // When the method has a breakpoint, it is not compiled. When the parameter - XX:-BackgroundCompilation is set to not background compilation, // It does not mean that the task is compiled in the user thread, but submits the task CompileTask to the CompileQueue. The only difference is to block the current thread waiting // CompileThread until the Task is compiled successfully blocking = is_compile_blocking(method, osr_bci); // Create compilation task task = create_compile_task( queue, compile_id, method, osr_bci, comp_level, hot_method, hot_count, comment, blocking ); } // Anonymous block end // If blocking is true, it means that you need to wait for the compilation task to complete before continuing to execute the method, such as OSR compilation if (blocking) { wait_for_completion(task); } }
Called create_ compile_ The task() function creates a compilation task. The implementation of the function is as follows:
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; }
Create a CompileTask task and add it to the task queue of C1 or C2. This function returns directly. This task is fetched from the queue by the C1 compiler thread or the C2 compiler thread, and then compiled. Whether the function submitting the task needs to wait for the compilation task to complete depends on the value of the BackgroundCompilation option. The default value is true, indicating asynchronous compilation, so it will not wait for the compilation to complete.
2. Take the task from the CompileQueue queue to compile
The call stack is as follows:
Threads::create_vm() thread.cpp CompileBroker::compilation_init() compileBroker.cpp
Compilation is called when a virtual machine instance is created_ Init() function initializes CompileBroker, compilation_ The init() function is responsible for initializing compilation related components, including compiler implementation, compilation threads, counters, etc. CompileBroker:: make is also called_ compiler_ Thread() function, in which the corresponding compilation thread is created and started, and the new thread will call compiler_ thread_ The loop () function takes the task from the CompileQueue to compile. The call stack of the new compilation thread started is as follows:
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
The compilation thread will continuously obtain the compilation task from the compilation task queue, and then compile the compiler_ thread_ The loop() function is implemented as follows:
void CompileBroker::compiler_thread_loop() { CompilerThread* thread = CompilerThread::current(); CompileQueue* queue = thread->queue(); // Only when the compiler fails to initialize due to insufficient CodeCache memory during initialization, compilation will be disabled // If compilation is not disabled, the initialization is normal, and is_ compilation_ disabled_ The forever() function always returns false while (!is_compilation_disabled_forever()) { HandleMark hm(thread); // Get a new task from the compilation queue. If there is no new compilation task, the get() method waits for 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()); // Compilation occurs only if there are no breakpoints if (method()->number_of_breakpoints() == 0) { // Compile the method. if ((UseCompiler || AlwaysCompileLoopMethods) && CompileBroker::should_compile_new_jobs()) { ... invoke_compiler_on_method(task); // Compile the compilation task } else { // If compilation has been prohibited, the compilation task in the queue is removed method->clear_queued_for_compilation(); } } } shutdown_compiler_runtime(thread->compiler(), thread); }
After the compilation thread is started, it automatically executes the above function, circularly takes out the compilation task from the compilation task queue to perform compilation, and makes some judgments after obtaining a new compilation task. For example, if the method has a breakpoint, it will not be compiled. square
Let's look at invoke_ compiler_ on_ The method() function is implemented as follows:
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); // After the Method * is passed in, the ciMethod * is returned. The ciMethod will be operated during compilation* ciMethod* target = ci_env.get_method_from_handle(target_handle); AbstractCompiler *comp = compiler(task_level); comp->compile_method(&ci_env, target, osr_bci); } // ... }
Note that whether it is OSR or method compilation, compiler:: compile will eventually be called_ Method() to compile.
Here we also need to pay attention to a series of classes starting with ci, such as ciEnv and ciMethod. ci is the abbreviation of compiler interface. Since there are various compilers in HotSpot VM, compiler related interfaces are specially defined. When we need to compile methods, the relevant data will be encapsulated as some class instances starting with ci. For example, call get_ Method_ from_ The handle () Method encapsulates Method * as ciMethod.
The implementation of the compiler() function is as follows:
static AbstractCompiler* compiler(int comp_level) { if (is_c2_compile(comp_level)) return _compilers[1]; // C2 compiler if (is_c1_compile(comp_level)) return _compilers[0]; // C1 compiler return NULL; }
For C1, the Compiler of the Compiler will be called_ Method() function; For C2, the compile of C2Compiler will be called_ Method() function. Here we only look at the compilation of C1 and the compilation:: compile called_ The important implementation of the method() function is as follows:
// Compile method or OSR int frame_size = compile_java_method(); // ... if (InstallMethods) { // The default value of the InstallMethods option is true // Installation method or OSR install_code(frame_size); }
Call compile_java_method() function compilation. This method is the entry method of C1 compiler. Later, when introducing C1 compiler, we will start with the implementation of this function. After the compilation is completed, call install_. The code() function "installs" the code. The next article will detail the process of installing and uninstalling code after compiling OSR and methods.
The official account is analyzed in depth. Java virtual machine HotSpot has updated the VM source code to analyze the related articles to 60+. Welcome to the attention. If there are any problems, add WeChat mazhimazh, pull you into the virtual cluster communication.