当前位置: 首页>后端>正文

jvm(5) jvm解释器与jit源码流程

前言:

一直以来听说代码具有两种解释模式,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;
}


https://www.xamrdz.com/backend/3jq1920718.html

相关文章: