前言:
一直以来听说代码具有两种解释模式,jit通过热点代码探测触发代码优化,所以想看看解释器执行的源码,汇编部分没有深入,等时机到了再看
java的跨平台是由jvm和class实现的,其他比如php,python可以通过特定的编译器生成class代码,而不同的平台实现需要解释器来将class代码翻译成机器语言
目前常用的就是jit和解释器两种的mixed-mode
code方法体里面关键字在代码里面会翻译成固定的机器指令,比如new ,renturn,方法调用等
在方法上面的关键字如static,public,synchronized(在方法体内又作为monitor出现)是作为方法描述符
class的指令代码的在bytecode.cpp,实现在TemplateTable的实现类上面
jvm初始化时init_globals中的interpreter_init_code()这里调用到了TemplateTable::initialize()
void TemplateInterpreter::initialize_code() {
AbstractInterpreter::initialize();
//TemplateTable将字节码中byteCode像new ,monitor,iload等做了一个映射
//可看到里面的def方法实际就是映射字节码,对应指令实现在templateTable_x86.cpp等里面
TemplateTable::initialize();
// generate interpreter
{ ResourceMark rm;
TraceTime timer("Interpreter generation", TRACETIME_LOG(Info, startuptime));
TemplateInterpreterGenerator g(_code);
// Free the unused memory not occupied by the interpreter and the stubs
_code->deallocate_unused_tail();
}
//...
}
//TemplateInterpreterGenerator g(_code)
TemplateInterpreterGenerator::TemplateInterpreterGenerator(StubQueue* _code): AbstractInterpreterGenerator(_code) {
_unimplemented_bytecode = NULL;
_illegal_bytecode_sequence = NULL;
generate_all();
}
generate_all中生成了代码的一个stubqueue,queue中包含了bytecode和一些method_entry,其中就生成了方法的执行过程,获取pc执行指令,dispatch_next指令流转等操作
入口main的java实际的方法调用为,因为最开始c++启动main后才有后续java方法的调用,所以先看main
JavaCalls::call(&result, h_method, &args, CHECK);
//其调用的是JavaCalls::call_helper
//解释代码,最后代码在CompileTask* CompileBroker::create_compile_task向前面初始化的解释器队列发送任务
CompilationPolicy::compile_if_required(method, CHECK);
//entry_point是解释后代码的地址,在link_method时设置初始化的地址,和解释器一样,后续有jit则会被替换
address entry_point = method->from_interpreted_entry();
if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
entry_point = method->interpreter_entry();//解释器_i2i_entry
}
//
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,//此处传入解释后方法地址
parameter_address,
args->size_of_parameters(),
CHECK
);
call_stub实现的方法是stubGenerator_x86_64.cpp中的generate_call_stub,其调用了解释后的地址
// call Java function
__ BIND(parameters_done);
__ movptr(rbx, method); // get Method*
//将c_rarg1指向了方法
__ movptr(c_rarg1, entry_point); // get entry_point
__ mov(r13, rsp); // set sender sp
BLOCK_COMMENT("call Java function");
//调用方法
__ call(c_rarg1);
最外层的java方法执行流程就是如此,后续main方法调用其他容器启动了项目再进行执行,java方法如何流转的还得看看解释器
解释模式
类加载连接初始化的连接中的link_method进行了解释
void Method::link_method(const methodHandle& h_method, TRAPS) {
// If the code cache is full, we may reenter this function for the
// leftover methods that weren't linked.
if (_i2i_entry != NULL) {
return;
}
assert( _code == NULL, "nothing compiled yet" );
// Setup interpreter entrypoint
assert(this == h_method(), "wrong h_method()" );
assert(adapter() == NULL, "init'd to NULL");
//此处进行了代码解释
address entry = Interpreter::entry_for_method(h_method);
assert(entry != NULL, "interpreter entry must be non-null");
// Sets both _i2i_entry and _from_interpreted_entry
//解释后的机器码地址设置到method,被entry_point = method->interpreter_entry()调用
set_interpreter_entry(entry);
// Don't overwrite already registered native entries.
if (is_native() && !has_native_function()) {
set_native_function(
SharedRuntime::native_method_throw_unsatisfied_link_error_entry(),
!native_bind_event_is_interesting);
}
(void) make_adapters(h_method, CHECK);
// ONLY USE the h_method now as make_adapter may have blocked
}
address entry = Interpreter::entry_for_method(h_method);
通过method的intrinsic_id判断是否是系统方法,不是的话最后返回zerolocals,通过之前generate_all生成的_entry_table模板找到对应方法入口
jit模式
jit初始化
在初始化时Threads::create_vm里面开启了jit的异步任务处理线程sweeper
具体用c1还是c2看参数
//创建线程在init_compiler_sweeper_threads();
CompileBroker::compilation_init_phase1(CHECK_JNI_ERR);
// Postpone completion of compiler initialization to after JVMCI
// is initialized to avoid timeouts of blocking compilations.
if (JVMCI_ONLY(!force_JVMCI_intialization) NOT_JVMCI(true)) {
CompileBroker::compilation_init_phase2();
}
这里创建了c1的解释器线程
_c1_compile_queue = new CompileQueue("C1 compile queue");
最后线程启动前
switch (type) {
case compiler_t:
assert(comp != NULL, "Compiler instance missing.");
if (!InjectCompilerCreationFailure || comp->num_compiler_threads() == 0) {
CompilerCounters* counters = new CompilerCounters();
//线程中包含任务队列和计数器
new_thread = new CompilerThread(queue, counters);
}
break;
case sweeper_t:
new_thread = new CodeCacheSweeperThread();
break;
new_thread = new CompilerThread(queue, counters);
这里就是解释器线程创建的地方,其中thread_entry就是run方法
CompilerThread::CompilerThread(CompileQueue* queue,
CompilerCounters* counters)
: JavaThread(&CompilerThread::thread_entry) {
_env = NULL;
_log = NULL;
_task = NULL;
_queue = queue;
_counters = counters;
_buffer_blob = NULL;
_compiler = NULL;
// Compiler uses resource area for compilation, let's bias it to mtCompiler
resource_area()->bias_to(mtCompiler);
#ifndef PRODUCT
_ideal_graph_printer = NULL;
#endif
}
void CompilerThread::thread_entry(JavaThread* thread, TRAPS) {
assert(thread->is_Compiler_thread(), "must be compiler thread");
CompileBroker::compiler_thread_loop();
}
jit异步线程不是java线程一般可视化工具看不到,可以通过jstat {pid} 的方式看到有Sweeper thread(清理线程)以及C1 CompilerThread,这个用来监视方法调用情况,达到阈值过后就进行jit代码优化
C:\Users\tango>jstack 24644
2024-01-04 21:05:13
Full thread dump OpenJDK 64-Bit Server VM (17.0.2+8-86 mixed mode, emulated-client, sharing):
Threads class SMR info:
_java_thread_list=0x000001f579ede7f0, length=53, elements={
0x000001f572f46b80, 0x000001f572f477f0, 0x000001f572f5c1a0, 0x000001f572f5cd20,
0x000001f572f66b00, 0x000001f572f674b0, 0x000001f572f67e60, 0x000001f572f68850,
0x000001f572f300c0, 0x000001f573a44790, 0x000001f573a45a10, 0x000001f573a4c110,
0x000001f573a83e40, 0x000001f57468cdd0, 0x000001f574c70510, 0x000001f5750e2cf0,
0x000001f5750e14e0, 0x000001f5750e1e80, 0x000001f5750e19b0, 0x000001f5750e31c0,
0x000001f579e18ac0, 0x000001f579efb0a0, 0x000001f579efba40, 0x000001f579efb570,
0x000001f574723770, 0x000001f574724110, 0x000001f574723c40, 0x000001f574722900,
0x000001f574722dd0, 0x000001f5747232a0, 0x000001f57ceffdc0, 0x000001f57cf00760,
0x000001f57cf00290, 0x000001f57cf02de0, 0x000001f57cf00c30, 0x000001f579efbf10,
0x000001f579efc8b0, 0x000001f579efc3e0, 0x000001f57d027c50, 0x000001f57d0285f0,
0x000001f57d028ac0, 0x000001f57d028f90, 0x000001f57d027780, 0x000001f57d028120,
0x000001f57a124260, 0x000001f57a123d90, 0x000001f57962e3c0, 0x000001f57a1238c0,
0x000001f579630570, 0x000001f57a0a8560, 0x000001f5794bb730, 0x000001f579e185f0,
0x000001f57e6c40a0
}
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 cpu=0.00ms elapsed=558.57s tid=0x000001f572f5c1a0 nid=0x37d4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 cpu=171.88ms elapsed=558.57s tid=0x000001f572f5cd20 nid=0x574 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Service Thread" #6 daemon prio=9 os_prio=0 cpu=15.62ms elapsed=558.57s tid=0x000001f572f66b00 nid=0x744 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Deflation Thread" #7 daemon prio=9 os_prio=0 cpu=0.00ms elapsed=558.57s tid=0x000001f572f674b0 nid=0x2f48 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
//c1解释线程
"C1 CompilerThread0" #8 daemon prio=9 os_prio=2 cpu=1109.38ms elapsed=558.57s tid=0x000001f572f67e60 nid=0x50e4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
No compile task
//清理解释线程
"Sweeper thread" #20 daemon prio=9 os_prio=2 cpu=93.75ms elapsed=558.57s tid=0x000001f572f68850 nid=0x618c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
......
jit任务添加
在javacall调用stub之前的方法中
CompilationPolicy::compile_if_required(method, CHECK);
nmethod* nm = CompileBroker::compile_method
CompileBroker::compile_method_base
会将方法在解释结果缓存里面进行查询method对应的nmethod,如果没有就创建相应的task
void CompileBroker::compile_method_base(const methodHandle& method,
int osr_bci,
int comp_level,
const methodHandle& hot_method,
int hot_count,
CompileTask::CompileReason compile_reason,
bool blocking,
Thread* thread) {
//......
task = create_compile_task(queue,
compile_id, method,
osr_bci, comp_level,
hot_method, hot_count, compile_reason,
blocking);
}
if (blocking) {
wait_for_completion(task);
}
}
任务放入了CompileQueue
CompileTask* CompileBroker::create_compile_task(CompileQueue* queue,
int compile_id,
const methodHandle& method,
int osr_bci,
int comp_level,
const methodHandle& hot_method,
int hot_count,
CompileTask::CompileReason compile_reason,
bool blocking) {
CompileTask* new_task = CompileTask::allocate();
new_task->initialize(compile_id, method, osr_bci, comp_level,
hot_method, hot_count, compile_reason,
blocking);
queue->add(new_task);
return new_task;
}
编译任务后台执行
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;
// First thread to get here will initialize the compiler interface
{
ASSERT_IN_VM;
MutexLocker only_one (thread, CompileThread_lock);
if (!ciObjectFactory::is_initialized()) {
ciObjectFactory::initialize();
}
}
// Open a log.
CompileLog* log = get_log(thread);
if (log != NULL) {
log->begin_elem("start_compile_thread name='%s' thread='" UINTX_FORMAT "' process='%d'",
thread->name(),
os::current_thread_id(),
os::current_process_id());
log->stamp();
log->end_elem();
}
// If compiler thread/runtime initialization fails, exit the compiler thread
if (!init_compiler_runtime()) {
return;
}
thread->start_idle_timer();
// 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);
CompileTask* task = queue->get();
if (task == NULL) {
if (UseDynamicNumberOfCompilerThreads) {
// Access compiler_count under lock to enforce consistency.
MutexLocker only_one(CompileThread_lock);
if (can_remove(thread, true)) {
if (TraceCompilerThreads) {
tty->print_cr("Removing compiler thread %s after " JLONG_FORMAT " ms idle time",
thread->name(), thread->idle_time_millis());
}
// Free buffer blob, if allocated
if (thread->get_buffer_blob() != NULL) {
MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag);
CodeCache::free(thread->get_buffer_blob());
}
return; // Stop this thread.
}
}
} else {
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()) {
//解释任务处理
invoke_compiler_on_method(task);
thread->start_idle_timer();
} else {
// After compilation is disabled, remove remaining methods from queue
method->clear_queued_for_compilation();
task->set_failure_reason("compilation is disabled");
}
}
if (UseDynamicNumberOfCompilerThreads) {
possibly_add_compiler_threads(thread);
assert(!thread->has_pending_exception(), "should have been handled");
}
}
}
// Shut down compiler runtime
shutdown_compiler_runtime(thread->compiler(), thread);
}
处理任务时会进行编译和方法计数初始化
void CompileBroker::invoke_compiler_on_method(CompileTask* task) {
//...
//实际上的comp是在初始化vm时的compilation_init_phase1,定义了comp的数组,一个c1一个c2,这里用的c1
AbstractCompiler* comp = task->compiler();
//...
if (comp == NULL) {
ci_env.record_method_not_compilable("no compiler");
} else if (!ci_env.failing()) {
if (WhiteBoxAPI && WhiteBox::compilation_locked) {
MonitorLocker locker(Compilation_lock, Mutex::_no_safepoint_check_flag);
while (WhiteBox::compilation_locked) {
locker.wait();
}
}
comp->compile_method(&ci_env, target, osr_bci, true, directive);
/* Repeat compilation without installing code for profiling purposes */
int repeat_compilation_count = directive->RepeatCompilationOption;
while (repeat_compilation_count > 0) {
task->print_ul("NO CODE INSTALLED");
comp->compile_method(&ci_env, target, osr_bci, false , directive);
repeat_compilation_count--;
}
}
//...
}
comp的初始化,后续代码通过comp_level获取对应的c1或c2,level分为五个等级,对应了纯解释器到c1再到c2
if (_c1_count > 0) {
_compilers[0] = new Compiler();
}
所以要看到c1_Compiler.cpp#compile_method
调用到c1_compilation.cpp#Compilation::Compilation
Compilation::Compilation(AbstractCompiler* compiler, ciEnv* env, ciMethod* method,
int osr_bci, BufferBlob* buffer_blob, bool install_code, DirectiveSet* directive){
//...
compile_method();
if (bailed_out()) {
_env->record_method_not_compilable(bailout_msg());
//根据level查看是否启用了hotspot优化
if (is_profiling()) {
// Compilation failed, create MDO, which would signal the interpreter
// to start profiling on its own.
_method->ensure_method_data();
}
} else if (is_profiling()) {
ciMethodData *md = method->method_data_or_null();
if (md != NULL) {
md->set_would_profile(_would_profile);
}
}
}
编译后的代码替换
void Compilation::compile_method() {
//...处理断点和日志等
// compile method
int frame_size = compile_java_method();
CHECK_BAILOUT();
if (should_install_code()) {
// install code
PhaseTraceTime timeit(_t_codeinstall);
//将编译后的nmethod(native methods)替换原来的method
install_code(frame_size);
}
//...
}
jit如何判断hot count
热点计数方式两种:方法调用计数和循环次数计数
1.方法调用计数
vm初始化时generate_all()
#define method_entry(kind) \
{ CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \
Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \
}
//...
普通java代码会走到generate_method_entry的entry_point = generate_normal_entry(synchronized);
其中的代码会 generate_counter_incr(&invocation_counter_overflow);这里就是方法调用计数
每有一次javacall就增加一次
2.循环次数
在TemplateTable::branch关键字当中,被_goto(goto_w,比前面支持的地址更长),jsr(jsr_w)使用
_goto指令是for和while循环的关键字
jsr是1.6版本之前用于try-finally控制返回的关键字
//branch方法调用增加次数的地方
if (ProfileInterpreter) {
// Are we profiling?
__ movptr(rbx, Address(rcx, in_bytes(Method::method_data_offset())));
__ testptr(rbx, rbx);
__ jccb(Assembler::zero, no_mdo);
// Increment the MDO backedge counter
const Address mdo_backedge_counter(rbx, in_bytes(MethodData::backedge_counter_offset()) +
in_bytes(InvocationCounter::counter_offset()));
const Address mask(rbx, in_bytes(MethodData::backedge_mask_offset()));
__ increment_mask_and_jump(mdo_backedge_counter, increment, mask, rax, false, Assembler::zero,
UseOnStackReplacement &backedge_counter_overflow : NULL);
__ jmp(dispatch);
}
3.次数的使用
所以每次javaCall能够增加次数,在代码提交了编译任务后,也不一定能够马上执行
在jit后台线程中CompileBroker::compiler_thread_loop()中消费编译任务
//从队列循环获取任务
CompileTask* task = queue->get();
//对应方法
CompileTask* CompileQueue::get(){
//...
task = CompilationPolicy::select_task(this);
//...
}
//转到select中
CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue) {
CompileTask *max_blocking_task = NULL;
CompileTask *max_task = NULL;
Method* max_method = NULL;
jlong t = nanos_to_millis(os::javaTimeNanos());
// Iterate through the queue and find a method with a maximum rate.
//循环编译队列,选出最优先的进行下一步编译
for (CompileTask* task = compile_queue->first(); task != NULL;) {
CompileTask* next_task = task->next();
Method* method = task->method();
// If a method was unloaded or has been stale for some time, remove it from the queue.
// Blocking tasks and tasks submitted from whitebox API don't become stale
//is_old代码method->invocation_count() > 50000 || method->backedge_count() > 500000 ;热点计数当前版本就是50000,如果第一条就是热点的就不需要继续循环了
if (task->is_unloaded() || (task->can_become_stale() && is_stale(t, TieredCompileTaskTimeout, method) && !is_old(method))) {
if (!task->is_unloaded()) {
if (PrintTieredEvents) {
print_event(REMOVE_FROM_QUEUE, method, method, task->osr_bci(), (CompLevel) task->comp_level());
}
method->clear_queued_for_compilation();
}
compile_queue->remove_and_mark_stale(task);
task = next_task;
continue;
}
//根据两种热点计数方式更新比重,并且进行判断
update_rate(t, method);
if (max_task == NULL || compare_methods(method, max_method)) {
// Select a method with the highest rate
max_task = task;
max_method = method;
}
if (task->is_blocking()) {
if (max_blocking_task == NULL || compare_methods(method, max_blocking_task->method())) {
max_blocking_task = task;
}
}
task = next_task;
}
//...
return max_task;
}