接着前面文章介绍了JVM通过JavaCalls::call方法,接着调用call_helper,然后调用CallStub调用java的方法的,CallStub结构体中entry_point是Java方法的入口地址,它是在哪里生成的呢? 那么今天主要来分析下模板解释器的入口地址,关于JIT编译器暂时先看. 可以看到JavaCalls::call_help中是通过method的_from_interpreted_entry字段作为method的方法入口地址.那么方法入口地址是在哪里设置的呢?就是方法链接的时候进行设置方法执行入口地址。
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
//省略
address entry_point = method->from_interpreted_entry();
}
method的链接
method.cpp中link_method就是方法的链接工作,
- 判断_i2i_entry如果已经存在,则表示已经链接,则直接返回。
- Interpreter::entry_for_method的获取编译器的入口地址
- set_interpreter_entry设置_from_interpreted_entry,_i2i_entry这个两个字段值为第二步获取方法入口地址.
- make_adapters做适配器,是为了解释器和编译器之间转换.
void Method::link_method(methodHandle h_method, TRAPS) {
if (_i2i_entry != NULL) return;
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
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);
}
下面是Interpreter父类AbstractInterpreter的entry_for_method方法,
- 调用entry_for_kind获取方法类型.
- 通过方法类型去_entry_table获取对应的方法入口地址.
AbstractInterpreter{
static address entry_for_kind(MethodKind k) { assert(0 <= k && k < number_of_method_entries, "illegal kind"); return _entry_table[k]; }
static address entry_for_method(methodHandle m) { return entry_for_kind(method_kind(m)); }
}
解释器的生成方法_entry_table入口表
首先看下发方法类型的枚举如下,JVM为以下每一种类型方法生成不同的方法入口指令的入口地址.我们先主要看zerolocal类型就是不同的java方法入口.
enum MethodKind {
zerolocals, // method needs locals initialization
zerolocals_synchronized, // method needs locals initialization & is synchronized
native, // native method
native_synchronized, // native method & is synchronized
empty, // empty method (code: _return)
accessor, // accessor method (code: _aload_0, _getfield, _(a|i)return)
abstract, // abstract method (throws an AbstractMethodException)
method_handle_invoke_FIRST, // java.lang.invoke.MethodHandles::invokeExact, etc.
method_handle_invoke_LAST = (method_handle_invoke_FIRST
+ (vmIntrinsics::LAST_MH_SIG_POLY
- vmIntrinsics::FIRST_MH_SIG_POLY)),
java_lang_math_sin, // implementation of java.lang.Math.sin (x)
java_lang_math_cos, // implementation of java.lang.Math.cos (x)
java_lang_math_tan, // implementation of java.lang.Math.tan (x)
java_lang_math_abs, // implementation of java.lang.Math.abs (x)
java_lang_math_sqrt, // implementation of java.lang.Math.sqrt (x)
java_lang_math_log, // implementation of java.lang.Math.log (x)
java_lang_math_log10, // implementation of java.lang.Math.log10 (x)
java_lang_math_pow, // implementation of java.lang.Math.pow (x,y)
java_lang_math_exp, // implementation of java.lang.Math.exp (x)
java_lang_ref_reference_get, // implementation of java.lang.ref.Reference.get()
java_util_zip_CRC32_update, // implementation of java.util.zip.CRC32.update()
java_util_zip_CRC32_updateBytes, // implementation of java.util.zip.CRC32.updateBytes()
java_util_zip_CRC32_updateByteBuffer, // implementation of java.util.zip.CRC32.updateByteBuffer()
number_of_method_entries,
invalid = -1
};
方法入口表是宏定义中生成
#define method_entry(kind) \
{ CodeletMark cm(_masm, "method entry point (kind = " #kind ")"); \
Interpreter::_entry_table[Interpreter::kind] = generate_method_entry(Interpreter::kind); \
}
// all non-native method kinds
method_entry(zerolocals)
method_entry(zerolocals_synchronized)
method_entry(empty)
method_entry(accessor)
method_entry(abstract)
method_entry(java_lang_math_sin )
method_entry(java_lang_math_cos )
method_entry(java_lang_math_tan )
method_entry(java_lang_math_abs )
method_entry(java_lang_math_sqrt )
method_entry(java_lang_math_log )
method_entry(java_lang_math_log10)
method_entry(java_lang_math_exp )
method_entry(java_lang_math_pow )
method_entry(java_lang_ref_reference_get)
initialize_method_handle_entries();
#undef method_entry
上面宏定义中会调用父类的generate_method_entry方法生成方法入口.
address AbstractInterpreterGenerator::generate_method_entry(AbstractInterpreter::MethodKind kind) {
// determine code generation flags
bool synchronized = false;
address entry_point = NULL;
InterpreterGenerator* ig_this = (InterpreterGenerator*)this;
switch (kind) {
case Interpreter::zerolocals : break;
case Interpreter::zerolocals_synchronized: synchronized = true; break;
case Interpreter::native : entry_point = ig_this->generate_native_entry(false); break;
case Interpreter::native_synchronized : entry_point = ig_this->generate_native_entry(true); break;
case Interpreter::empty : entry_point = ig_this->generate_empty_entry(); break;
case Interpreter::accessor : entry_point = ig_this->generate_accessor_entry(); break;
case Interpreter::abstract : entry_point = ig_this->generate_abstract_entry(); break;
case Interpreter::java_lang_math_sin : // fall thru
case Interpreter::java_lang_math_cos : // fall thru
case Interpreter::java_lang_math_tan : // fall thru
case Interpreter::java_lang_math_abs : // fall thru
case Interpreter::java_lang_math_log : // fall thru
case Interpreter::java_lang_math_log10 : // fall thru
case Interpreter::java_lang_math_sqrt : // fall thru
case Interpreter::java_lang_math_pow : // fall thru
case Interpreter::java_lang_math_exp : entry_point = ig_this->generate_math_entry(kind); break;
case Interpreter::java_lang_ref_reference_get
: entry_point = ig_this->generate_Reference_get_entry(); break;
case Interpreter::java_util_zip_CRC32_update
: entry_point = ig_this->generate_CRC32_update_entry(); break;
case Interpreter::java_util_zip_CRC32_updateBytes
: // fall thru
case Interpreter::java_util_zip_CRC32_updateByteBuffer
: entry_point = ig_this->generate_CRC32_updateBytes_entry(kind); break;
default:
fatal(err_msg("unexpected method kind: %d", kind));
break;
}
if (entry_point) return entry_point;
return ig_this->generate_normal_entry(synchronized);
}
下面是generate_normal_entry方法体(这个是templateInterpreter的32位平台的生成逻辑)。
address InterpreterGenerator::generate_normal_entry方法提。(bool synchronized) {
bool inc_counter = UseCompiler || CountCompiledCalls;
// rbx,: Method*
// rsi: sender sp
address entry_point = __ pc();
const Address constMethod (rbx, Method::const_offset());
const Address access_flags (rbx, Method::access_flags_offset());
const Address size_of_parameters(rdx, ConstMethod::size_of_parameters_offset());
const Address size_of_locals (rdx, ConstMethod::size_of_locals_offset());
// get parameter size (always needed)
__ movptr(rdx, constMethod);
__ load_unsigned_short(rcx, size_of_parameters);
// rbx,: Method*
// rcx: size of parameters
// rsi: sender_sp (could differ from sp+wordSize if we were called via c2i )
__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
__ subl(rdx, rcx); // rdx = no. of additional locals
// see if we've got enough room on the stack for locals plus overhead.
generate_stack_overflow_check();
// get return address
__ pop(rax);
// compute beginning of parameters (rdi)
__ lea(rdi, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));
// rdx - # of additional locals
// allocate space for locals
// explicitly initialize locals
{
Label exit, loop;
__ testl(rdx, rdx);
__ jcc(Assembler::lessEqual, exit); // do nothing if rdx <= 0
__ bind(loop);
__ push((int32_t)NULL_WORD); // initialize local variables
__ decrement(rdx); // until everything initialized
__ jcc(Assembler::greater, loop);
__ bind(exit);
}
// initialize fixed part of activation frame
generate_fixed_frame(false);
// Since at this point in the method invocation the exception handler
// would try to exit the monitor of synchronized methods which hasn't
// been entered yet, we set the thread local variable
// _do_not_unlock_if_synchronized to true. The remove_activation will
// check this flag.
__ get_thread(rax);
const Address do_not_unlock_if_synchronized(rax, in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
__ movbool(do_not_unlock_if_synchronized, true);
__ profile_parameters_type(rax, rcx, rdx);
// increment invocation count & check for overflow
Label invocation_counter_overflow;
Label profile_method;
Label profile_method_continue;
if (inc_counter) {
generate_counter_incr(&invocation_counter_overflow, &profile_method, &profile_method_continue);
if (ProfileInterpreter) {
__ bind(profile_method_continue);
}
}
Label continue_after_compile;
__ bind(continue_after_compile);
bang_stack_shadow_pages(false);
// reset the _do_not_unlock_if_synchronized flag
__ get_thread(rax);
__ movbool(do_not_unlock_if_synchronized, false);
// 开始分派执行java的字节码指令
__ dispatch_next(vtos);
// invocation counter overflow
if (inc_counter) {
if (ProfileInterpreter) {
// We have decided to profile this method in the interpreter
__ bind(profile_method);
__ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::profile_method));
__ set_method_data_pointer_for_bcp();
__ get_method(rbx);
__ jmp(profile_method_continue);
}
// Handle overflow of counter and compile method
__ bind(invocation_counter_overflow);
generate_counter_overflow(&continue_after_compile);
}
// jvmti support
__ notify_method_entry();
// 分派执行执行
__ dispatch_next(vtos);
return entry_point;
}
- __ pc() 方法,前面文章已经介绍过__它是宏定义,实际是InterpreterMacroAssembler的指针,就调用其父类AbstractAssembler的 pc()方法获取CodeSection对象的end地址.
class AbstractAssembler : public ResourceObj {
protected:
CodeSection* _code_section;
address pc() const {
return code_section()->end();
}
}
- 之前介绍过在调用方法入口前的寄存器rbx存的mthod的指针,rsi是调用者的rsp地址,下面这个行就是将constMethod的地址移动到rdx寄存器,
__ movptr(rdx, constMethod);
- 然后将参数大小的值加载到rcx寄存器中.
__ load_unsigned_short(rcx, size_of_parameters);
- 将局部变量表的大小移加载到rdx寄存器
__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
- 将rdx的值减去rcx寄存器的值,就等于除了参数以外的额外的本地变量个数,将剩余的需要额外的参数个数值保存到rdx寄存器.
__ subl(rdx, rcx);
- 校验栈溢出
generate_stack_overflow_check();
- 将栈上返回地址保存到rax寄存器中.
__ pop(rax);
- 将栈顶的地址向高地址偏移参数大小*栈中一个元素大小32位就是4byte),然后向下移动一个机器字长(32位也就是4byte),此时栈顶的位置就是执行第一个参数的位置.
__ lea(rdi, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));
- 接下者这段代码使用循环初始化本地变量表。\
- 首先定义Label标签 exit、loop.
- 然后将rdx与自己做and计算,判断rdx的数量小于0,说明不需要本地变量表,则直接跳转到exit标签.
- 如果rdx的值大于0,则说明需要初始化本地变量表, 然后在此插入一个loop标签
- 执行push操作,将NULL_WORD在GNUCi标准就是0,然后转换成32为int指针,实际就是本地变量初始化空的指针.
- 将rdx的值减1。
- 如果rdx的值大于0,则跳转bind(loop)的位置循环执行,直到所有的本地变量表都初始化完成.
{
Label exit, loop;
__ testl(rdx, rdx);
// do nothing if rdx
__ jcc(Assembler::lessEqual, exit); <= 0
__ bind(loop);
// initialize local
__ push((int32_t)NULL_WORD);
// until everything initialized
__ decrement(rdx);
__ jcc(Assembler::greater, loop);
__ bind(exit);
}
- 接下来就是生成java方法固定栈帧,这个方法先了解其作用,会在一篇单独细分析。
// initialize fixed part of activation frame
generate_fixed_frame(false);
- 将当前执行方法的线程对象移动rax寄存器,获取JavaThread线程的_do_not_unlock_if_synchronized的偏移,然后设置该线程本地变量为true。因为此时方法synchonized枷锁操作,由于此时还没有执行enter的操作,所以是不能执行unlock操作,所以在remove_activation的时候会检查这个标识.
__ get_thread(rax);
const Address do_not_unlock_if_synchronized(rax, in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
__ movbool(do_not_unlock_if_synchronized, true);
- 接下来这段就是进行方法调用次数统计,这里就不详细介绍。
__ profile_parameters_type(rax, rcx, rdx);
// increment invocation count & check for overflow
Label invocation_counter_overflow;
Label profile_method;
Label profile_method_continue;
if (inc_counter) {
generate_counter_incr(&invocation_counter_overflow, &profile_method, &profile_method_continue);
if (ProfileInterpreter) {
__ bind(profile_method_continue);
}
}
Label continue_after_compile;
__ bind(continue_after_compile);
bang_stack_shadow_pages(false);
- jvmti的回调函数支持.这里也就不详细说
// jvmti support
__ notify_method_entry();
- 这个就是Java执行字节码执行指令的关键入口。这个会在下面的文章介绍.
__ dispatch_next(vtos);
总结
本文主要分析了JVM生成java的普通方法的方法入口entry_point的汇编指令的代码的过程,可以了解JVM生成java栈帧的产地的参数是保存在调用者的栈帧中,方法内部的局部变量会初始化到java的栈帧中,还有JVM中定义栈帧动态链接地址和操作数栈还没有分析,以及是如何执行字节码的方法的code的字节码指令等,后面再分析。