jvm编译源码介绍
2022-02-07 本文已影响0人
allanYan
概述
jvm和编译相关的代码入口在compileBroker.cpp文件
nmethod* CompileBroker::compile_method(methodHandle method, int osr_bci,
int comp_level,
methodHandle hot_method, int hot_count,
const char* comment, Thread* THREAD)
那么什么情况下会触发java代码的编译呢?从下面的代码可以看到java方法调用时都会检查是否需要编译:
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
methodHandle method = *m;
JavaThread* thread = (JavaThread*)THREAD;
if (CompilationPolicy::must_be_compiled(method)) {
CompileBroker::compile_method(method, InvocationEntryBci,
CompilationPolicy::policy()->initial_compile_level(),
methodHandle(), 0, "must_be_compiled", CHECK);
}
// Since the call stub sets up like the interpreter we call the from_interpreted_entry
// so we can go compiled via a i2c. Otherwise initial entry method will always
// run interpreted.
address entry_point = method->from_interpreted_entry();
if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
entry_point = method->interpreter_entry();
}
// Figure out if the result value is an oop or not (Note: This is a different value
// than result_type. result_type will be T_INT of oops. (it is about size)
BasicType result_type = runtime_type_from(result);
bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);
// NOTE: if we move the computation of the result_val_address inside
// the call to call_stub, the optimizer produces wrong code.
intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
// Find receiver
Handle receiver = (!method->is_static()) ? args->receiver() : Handle();
// When we reenter Java, we need to reenable the yellow zone which
// might already be disabled when we are in VM.
if (thread->stack_yellow_zone_disabled()) {
thread->reguard_stack();
}
// Check that there are shadow pages available before changing thread state
// to Java
if (!os::stack_shadow_pages_available(THREAD, method)) {
// Throw stack overflow exception with preinitialized exception.
Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method);
return;
} else {
// Touch pages checked if the OS needs them to be touched to be mapped.
os::bang_stack_shadow_pages();
}
// do call
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread); // HandleMark used by HandleMarkCleaner
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);
result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
// Preserve oop return value across possible gc points
if (oop_result_flag) {
thread->set_vm_result((oop) result->get_jobject());
}
}
} // Exit JavaCallWrapper (can block - potential return oop must be preserved)
// Check if a thread stop or suspend should be executed
// The following assert was not realistic. Thread.stop can set that bit at any moment.
//assert(!thread->has_special_runtime_exit_condition(), "no async. exceptions should be installed");
// Restore possible oop return
if (oop_result_flag) {
result->set_jobject((jobject)thread->vm_result());
thread->set_vm_result(NULL);
}
}
bool CompilationPolicy::must_be_compiled(methodHandle m, int comp_level) {
// Don't allow Xcomp to cause compiles in replay mode
if (ReplayCompiles) return false;
if (m->has_compiled_code()) return false; // already compiled
if (!can_be_compiled(m, comp_level)) return false;
return !UseInterpreter || // must compile all methods
(UseCompiler && AlwaysCompileLoopMethods && m->has_loops() && CompileBroker::should_compile_new_jobs()); // eagerly compile loop methods
}
可以看到,如果方法已经编译过,则不需要再次编译;另外jvm默认不会编译巨型方法,相关的配置项为:
-XX:+DontCompileHugeMethods
-XX:-DontCompileHugeMethods
判断方法是否为大对象由-XX:HugeMethodLimit=8000来决定,8000表示JIT编译字节码大小超过8000字节的方法就是巨型方法,这个阈值在HotSpot里是不支持调整的;
编译流程
上面可以看到,java方法执行时会检查是否需要进行编译;如果需要编译,jvm是如何将java代码编译成native代码的呢?还是回到 CompileBroker::compile_method
方法,调用的链路为compile_method->compile_method_base->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;
}
可以看到,jvm将编译请求封装成CompileTask
,并加入到队列中,那么队列中的任务是由哪个线程处理的呢?
jvm启动时,会通过Threads::create_vm->CompileBroker::compilation_init()->init_compiler_threads->make_compiler_thread
来创建编译线程,包括c1和c2线程;
CompilerThread* CompileBroker::make_compiler_thread(const char* name, CompileQueue* queue, CompilerCounters* counters,
AbstractCompiler* comp, TRAPS) {
CompilerThread* compiler_thread = NULL;
Klass* k =
SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(),
true, CHECK_0);
instanceKlassHandle klass (THREAD, k);
instanceHandle thread_oop = klass->allocate_instance_handle(CHECK_0);
Handle string = java_lang_String::create_from_str(name, CHECK_0);
// Initialize thread_oop to put it into the system threadGroup
Handle thread_group (THREAD, Universe::system_thread_group());
JavaValue result(T_VOID);
JavaCalls::call_special(&result, thread_oop,
klass,
vmSymbols::object_initializer_name(),
vmSymbols::threadgroup_string_void_signature(),
thread_group,
string,
CHECK_0);
{
MutexLocker mu(Threads_lock, THREAD);
compiler_thread = new CompilerThread(queue, counters);
// At this point the new CompilerThread data-races with this startup
// thread (which I believe is the primoridal thread and NOT the VM
// thread). This means Java bytecodes being executed at startup can
// queue compile jobs which will run at whatever default priority the
// newly created CompilerThread runs at.
// At this point it may be possible that no osthread was created for the
// JavaThread due to lack of memory. We would have to throw an exception
// in that case. However, since this must work and we do not allow
// exceptions anyway, check and abort if this fails.
if (compiler_thread == NULL || compiler_thread->osthread() == NULL){
vm_exit_during_initialization("java.lang.OutOfMemoryError",
"unable to create new native thread");
}
java_lang_Thread::set_thread(thread_oop(), compiler_thread);
// Note that this only sets the JavaThread _priority field, which by
// definition is limited to Java priorities and not OS priorities.
// The os-priority is set in the CompilerThread startup code itself
java_lang_Thread::set_priority(thread_oop(), NearMaxPriority);
// Note that we cannot call os::set_priority because it expects Java
// priorities and we are *explicitly* using OS priorities so that it's
// possible to set the compiler thread priority higher than any Java
// thread.
int native_prio = CompilerThreadPriority;
if (native_prio == -1) {
if (UseCriticalCompilerThreadPriority) {
native_prio = os::java_to_os_priority[CriticalPriority];
} else {
native_prio = os::java_to_os_priority[NearMaxPriority];
}
}
os::set_native_priority(compiler_thread, native_prio);
java_lang_Thread::set_daemon(thread_oop());
compiler_thread->set_threadObj(thread_oop());
compiler_thread->set_compiler(comp);
Threads::add(compiler_thread);
Thread::start(compiler_thread);
}
return compiler_thread;
}
CompilerThread::CompilerThread(CompileQueue* queue, CompilerCounters* counters)
: JavaThread(&compiler_thread_entry) {
_env = NULL;
_log = NULL;
_task = NULL;
_queue = queue;
_counters = counters;
_buffer_blob = NULL;
_scanned_nmethod = NULL;
_compiler = NULL;
#ifndef PRODUCT
_ideal_graph_printer = NULL;
#endif
}
接下来看看compiler_thread_entry的定义:
static void compiler_thread_entry(JavaThread* thread, TRAPS) {
assert(thread->is_Compiler_thread(), "must be compiler thread");
CompileBroker::compiler_thread_loop();
}
void CompileBroker::compiler_thread_loop() {
CompilerThread* thread = CompilerThread::current();
CompileQueue* queue = thread->queue();
// For the thread that initializes the ciObjectFactory
// this resource mark holds all the shared objects
ResourceMark rm;
// Poll for new compilation tasks as long as the JVM runs. Compilation
// should only be disabled if something went wrong while initializing the
// compiler runtimes. This, in turn, should not happen. The only known case
// when compiler runtime initialization fails is if there is not enough free
// space in the code cache to generate the necessary stubs, etc.
while (!is_compilation_disabled_forever()) {
// We need this HandleMark to avoid leaking VM handles.
HandleMark hm(thread);
if (CodeCache::unallocated_capacity() < CodeCacheMinimumFreeSpace) {
// the code cache is really full
handle_full_code_cache();
}
CompileTask* task = queue->get();
if (task == NULL) {
continue;
}
// Give compiler threads an extra quanta. They tend to be bursty and
// this helps the compiler to finish up the job.
if( CompilerThreadHintNoPreempt )
os::hint_no_preempt();
// trace per thread time and compile statistics
CompilerCounters* counters = ((CompilerThread*)thread)->counters();
PerfTraceTimedEvent(counters->time_counter(), counters->compile_counter());
// Assign the task to the current thread. Mark this compilation
// thread as active for the profiler.
CompileTaskWrapper ctw(task);
nmethodLocker result_handle; // (handle for the nmethod produced by this task)
task->set_code_handle(&result_handle);
methodHandle method(thread, task->method());
// Never compile a method if breakpoints are present in it
if (method()->number_of_breakpoints() == 0) {
// Compile the method.
if ((UseCompiler || AlwaysCompileLoopMethods) && CompileBroker::should_compile_new_jobs()) {
#ifdef COMPILER1
// Allow repeating compilations for the purpose of benchmarking
// compile speed. This is not useful for customers.
if (CompilationRepeat != 0) {
int compile_count = CompilationRepeat;
while (compile_count > 0) {
invoke_compiler_on_method(task);
nmethod* nm = method->code();
if (nm != NULL) {
nm->make_zombie();
method->clear_code();
}
compile_count--;
}
}
#endif /* COMPILER1 */
invoke_compiler_on_method(task);
} else {
// After compilation is disabled, remove remaining methods from queue
method->clear_queued_for_compilation();
}
}
}
// Shut down compiler runtime
shutdown_compiler_runtime(thread->compiler(), thread);
}
jvm编译配置
jvm提供了CompileCommand和CompileCommandFile来对jit编译进行定制
-XX:CompileCommand=exclude,Test.dummy
-XX:CompileCommand=exclude,java.lang.AbstractStringBuilder::append
-XX:CompileCommand=inline,AbstractMemoryEfficientList.equals
-XX:CompileCommand=dontinline,TestStackBangRbp::m1
-XX:CompileCommand=exclude,TestByteBoxing.dummy -XX:CompileCommand=exclude,TestByteBoxing.foo -XX:CompileCommand=exclude,TestByteBoxing.foob
-XX:CompileCommand=compileonly,*.foo*
-XX:CompileCommand=compileonly,TestCase$Helper::*
CompileCommand支持的选项如下:
- break
- exclude
- inline
- dontinline
- compileonly
- log
- option
- quiet
- help
附录
附上一个小工具,用于打印jvm codecache的具体内容
import sun.jvm.hotspot.code.CodeBlob;
import sun.jvm.hotspot.code.CodeCache;
import sun.jvm.hotspot.code.CodeCacheVisitor;
import sun.jvm.hotspot.code.NMethod;
import sun.jvm.hotspot.debugger.Address;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;
public class ScanCodeCache extends Tool {
public static void main(String[] args) {
ScanCodeCache cd = new ScanCodeCache();
cd.execute(args);
}
@Override
public void run() {
CodeCache codeCache = VM.getVM().getCodeCache();
codeCache.iterate(new CodeCacheVisitor() {
@Override
public void prologue(Address address, Address address1) {
}
@Override
public void visit(CodeBlob codeBlob) {
此处代码可以调整,根据自己的需要打印关心的内容
if (codeBlob instanceof NMethod) {
NMethod nMethod = (NMethod) codeBlob;
String name = nMethod.getName()
try {
nMethod.dumpReplayData(System.out);
} catch (Throwable e) {
}
}
}
@Override
public void epilogue() {
}
});
}
}
假设你的jdk安装在/usr/local/jdk目录,Java应用的pid为63:
- 编译: javac -cp .:/usr/local/jdk/lib/sa-jdi.jar ScanCodeCache.java
- 执行: java -cp .:/usr/local/jdk/lib/sa-jdi.jar ScanCodeCache 63 > codecache.txt