JIT编译源码介绍

概述

解释执行时会插入方法调用计数相关的逻辑:

--address TemplateInterpreterGenerator::generate_native_entry(bool synchronized)
--TemplateInterpreterGenerator::generate_normal_entry(bool synchronized)
if (inc_counter) {
    // Handle overflow of counter and compile method
    __ bind(invocation_counter_overflow);
    generate_counter_overflow(continue_after_compile);
  }

通过如下的调用链:
generate_counter_overflow->InterpreterRuntime::frequency_counter_overflow->CompilationPolicy.event->CompilationPolicy.method_invocation_event->CompilationPolicy::compile最终触发JIT编译

JIT编译条件

那么什么情况下会触发java代码的JIT编译呢?

// Determine if a method should be compiled with a normal entry point at a different level.
CompLevel CompilationPolicy::call_event(const methodHandle& method, CompLevel cur_level, Thread* thread) {
  CompLevel osr_level = MIN2((CompLevel) method->highest_osr_comp_level(), common(method, cur_level, true));
  CompLevel next_level = common(method, cur_level, is_old(method));

  // If OSR method level is greater than the regular method level, the levels should be
  // equalized by raising the regular method level in order to avoid OSRs during each
  // invocation of the method.
  if (osr_level == CompLevel_full_optimization && cur_level == CompLevel_full_profile) {
    MethodData* mdo = method->method_data();
    guarantee(mdo != NULL, "MDO should not be NULL");
    if (mdo->invocation_count() >= 1) {
      next_level = CompLevel_full_optimization;
    }
  } else {
    next_level = MAX2(osr_level, next_level);
  }
  return next_level;
}
class CallPredicate : AllStatic {
public:
static bool apply(const methodHandle& method, CompLevel cur_level, int i, int b) {
    double k = 1;
    switch(cur_level) {
    case CompLevel_none:
    case CompLevel_limited_profile: {
      k = CompilationPolicy::threshold_scale(CompLevel_full_profile, Tier3LoadFeedback);
      break;
    }
    case CompLevel_full_profile: {
      k = CompilationPolicy::threshold_scale(CompLevel_full_optimization, Tier4LoadFeedback);
      break;
    }
    default:
      return true;
    }
    return apply_scaled(method, cur_level, i, b, k);
  }
  static bool apply_scaled(const methodHandle& method, CompLevel cur_level, int i, int b, double scale) {
    double threshold_scaling;
    if (CompilerOracle::has_option_value(method, CompileCommand::CompileThresholdScaling, threshold_scaling)) {
      scale *= threshold_scaling;
    }
    switch(cur_level) {
    case CompLevel_none:
    case CompLevel_limited_profile:
      return (i >= Tier3InvocationThreshold * scale) ||
             (i >= Tier3MinInvocationThreshold * scale && i + b >= Tier3CompileThreshold * scale);//其中i表示方法调用的次数,b表示回边发生的次数,-XX:Tier3CompileThreshold默认为2000
    case CompLevel_full_profile:
      return (i >= Tier4InvocationThreshold * scale) ||
             (i >= Tier4MinInvocationThreshold * scale && i + b >= Tier4CompileThreshold * scale);
    default:
     return true;
    }
  }
};

// Call and loop predicates determine whether a transition to a higher
// compilation level should be performed (pointers to predicate functions
// are passed to common()).
// Tier?LoadFeedback is basically a coefficient that determines of
// how many methods per compiler thread can be in the queue before
// the threshold values double.
class LoopPredicate : AllStatic {
public:
  static bool apply_scaled(const methodHandle& method, CompLevel cur_level, int i, int b, double scale) {
    double threshold_scaling;
    if (CompilerOracle::has_option_value(method, CompileCommand::CompileThresholdScaling, threshold_scaling)) {
      scale *= threshold_scaling;
    }
    switch(cur_level) {
    case CompLevel_none:
    case CompLevel_limited_profile:
      return b >= Tier3BackEdgeThreshold * scale;
    case CompLevel_full_profile:
      return b >= Tier4BackEdgeThreshold * scale;
    default:
      return true;
    }
  }

可以看到有如下因素影响JIT编译:

  1. 方法调用次数;
  2. 循环次数
  3. 方法体大小

相关的JVM配置项如下:

  1. CompileThresholdScaling:默认值为1.0,控制是否触发编译的因子,J值越低,编译开始得越早,可以基于方法基本配置,例如 -XX:CompileCommand=CompileThresholdScaling,*MyBenchmark*::compute*,0.05
  2. Tier3InvocationThreshold:tier3编译的阈值(C1, invocation & backedge counters + mdo),默认值为200
  3. Tier3MinInvocationThreshold:tier3编译的最小调用次数阈值,默认为100
  4. Tier3CompileThreshold:默认值为2000,针对tier3;
  5. Tier4InvocationThreshold:默认值为5000,针对tier4;
  6. Tier4MinInvocationThreshold:默认值为600,针对tier4;
  7. Tier4CompileThreshold:默认值为15000,针对tier4;
    8.DontCompileHugeMethods和HugeMethodLimit:默认字节码大小超过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_phase1->CompileBroker::init_compiler_sweeper_threads->CompileBroker::make_thread来创建编译线程,包括c1和c2线程;

JavaThread* CompileBroker::make_thread(ThreadType type, jobject thread_handle, CompileQueue* queue, AbstractCompiler* comp, JavaThread* THREAD) {
  JavaThread* new_thread = NULL;
  {
    MutexLocker mu(THREAD, Threads_lock);
    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;
#if defined(ASSERT) && COMPILER2_OR_JVMCI
      case deoptimizer_t:
        new_thread = new DeoptimizeObjectsALotThread();
        break;
#endif // ASSERT
      default:
        ShouldNotReachHere();
    }

    // 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. But first release the
    // lock.

    if (new_thread != NULL && new_thread->osthread() != NULL) {

      java_lang_Thread::set_thread(JNIHandles::resolve_non_null(thread_handle), new_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(JNIHandles::resolve_non_null(thread_handle), 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(new_thread, native_prio);

      java_lang_Thread::set_daemon(JNIHandles::resolve_non_null(thread_handle));

      new_thread->set_threadObj(JNIHandles::resolve_non_null(thread_handle));
      if (type == compiler_t) {
        CompilerThread::cast(new_thread)->set_compiler(comp);
      }
      Threads::add(new_thread);
      Thread::start(new_thread);
    }
  }

  // First release lock before aborting VM.
  if (new_thread == NULL || new_thread->osthread() == NULL) {
    if (UseDynamicNumberOfCompilerThreads && type == compiler_t && comp->num_compiler_threads() > 0) {
      if (new_thread != NULL) {
        new_thread->smr_delete();
      }
      return NULL;
    }
    vm_exit_during_initialization("java.lang.OutOfMemoryError",
                                  os::native_thread_creation_failed_msg());
  }

  // Let go of Threads_lock before yielding
  os::naked_yield(); // make sure that the compiler thread is started early (especially helpful on SOLARIS)

  return new_thread;
}
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
}

接下来看看CompilerThread::thread_entry的定义:

void CompilerThread::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;

  // 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 {
      // Assign the task to the current thread.  Mark this compilation
      // thread as active for the profiler.
      // CompileTaskWrapper also keeps the Method* from being deallocated if redefinition
      // occurs after fetching the compile task off the queue.
      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");
        }
      } else {
        task->set_failure_reason("breakpoints are present");
      }

      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);
}

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

你可能感兴趣的:(JIT编译源码介绍)