概述
解释执行时会插入方法调用计数相关的逻辑:
--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编译:
- 方法调用次数;
- 循环次数
- 方法体大小
相关的JVM配置项如下:
- CompileThresholdScaling:默认值为1.0,控制是否触发编译的因子,J值越低,编译开始得越早,可以基于方法基本配置,例如
-XX:CompileCommand=CompileThresholdScaling,*MyBenchmark*::compute*,0.05
- Tier3InvocationThreshold:tier3编译的阈值(C1, invocation & backedge counters + mdo),默认值为200
- Tier3MinInvocationThreshold:tier3编译的最小调用次数阈值,默认为100
- Tier3CompileThreshold:默认值为2000,针对tier3;
- Tier4InvocationThreshold:默认值为5000,针对tier4;
- Tier4MinInvocationThreshold:默认值为600,针对tier4;
- 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支持的选项如下:
- 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