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支持的选项如下:

  1. break
  2. print
  3. exclude
  4. inline
  5. dontinline
  6. compileonly
  7. log
  8. option
  9. quiet
  10. 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:

  1. 编译: javac -cp .:/usr/local/jdk/lib/sa-jdi.jar ScanCodeCache.java
  2. 执行: java -cp .:/usr/local/jdk/lib/sa-jdi.jar ScanCodeCache 63 > codecache.txt
上一篇下一篇

猜你喜欢

热点阅读