JVM方法调用——java之间
Java方法之间
解释方法到解释方法
进入
解释方法到解释方法是最为简单的一种情况,最常见的调用是invokevirtual
。有关的代码在TemplateTable::invokevirtual
中:
void TemplateTable::invokevirtual(int byte_no) {
transition(vtos, vtos);
assert(byte_no == f2_byte, "use this argument");
// 让寄存器保存对应值,同时,保存rbcp以及确定返回地址。
prepare_invoke(byte_no,
rbx, // method or vtable index
noreg, // unused itable index
rcx, rdx); // recv, flags
// rbx: index(其实是method)
// rcx: receiver
// rdx: flags
invokevirtual_helper(rbx, rcx, rdx);
}
有关的逻辑并不多,所以可以看完它们,首先来看看prepare_invoke
:
void TemplateTable::prepare_invoke(int byte_no,
Register method, // linked method (or i-klass)
Register index, // itable index, MethodType, etc.
Register recv, // if caller wants to see it
Register flags // if caller wants to test it
) {
// determine flags
const Bytecodes::Code code = bytecode();
const bool is_invokeinterface = code == Bytecodes::_invokeinterface;
const bool is_invokedynamic = code == Bytecodes::_invokedynamic;
const bool is_invokehandle = code == Bytecodes::_invokehandle;
const bool is_invokevirtual = code == Bytecodes::_invokevirtual;
const bool is_invokespecial = code == Bytecodes::_invokespecial;
const bool load_receiver = (recv != noreg); // 非静态的调用。
const bool save_flags = (flags != noreg);
assert(load_receiver == (code != Bytecodes::_invokestatic && code != Bytecodes::_invokedynamic), "");
assert(save_flags == (is_invokeinterface || is_invokevirtual), "need flags for vfinal");
assert(flags == noreg || flags == rdx, "");
assert(recv == noreg || recv == rcx, "");
// setup registers & access constant pool cache
if (recv == noreg) recv = rcx;
if (flags == noreg) flags = rdx;
assert_different_registers(method, index, recv, flags);
// save 'interpreter return address'
__ save_bcp(); // 将当前rbcp保存在当前栈的rbcp中(还没有到新的栈)。
// 将方法指针放入method寄存器,将flags放入flags寄存器。
load_invoke_cp_cache_entry(byte_no, method, index, flags, is_invokevirtual, false, is_invokedynamic);
// maybe push appendix to arguments (just before return address)
if (is_invokedynamic || is_invokehandle) {
Label L_no_push;
__ testl(flags, (1 << ConstantPoolCacheEntry::has_appendix_shift));
__ jcc(Assembler::zero, L_no_push);
// Push the appendix as a trailing parameter.
// This must be done before we get the receiver,
// since the parameter_size includes it.
__ push(rbx);
__ mov(rbx, index);
assert(ConstantPoolCacheEntry::_indy_resolved_references_appendix_offset == 0, "appendix expected at index+0");
__ load_resolved_reference_at_index(index, rbx);
__ pop(rbx);
__ push(index); // push appendix (MethodType, CallSite, etc.)
__ bind(L_no_push);
}
// load receiver if needed (after appendix is pushed so parameter size is correct)
// Note: no return address pushed yet
if (load_receiver) {
__ movl(recv, flags);
__ andl(recv, ConstantPoolCacheEntry::parameter_size_mask);
const int no_return_pc_pushed_yet = -1; // argument slot correction before we push return address
const int receiver_is_at_end = -1; // back off one slot to get receiver
Address recv_addr = __ argument_address(recv, no_return_pc_pushed_yet + receiver_is_at_end);
__ movptr(recv, recv_addr); // 通过对rsp的偏移获取receiver。
__ verify_oop(recv);
}
if (save_flags) {
// 原有的rbcp已经保存了。
__ movl(rbcp, flags);
}
// compute return type
__ shrl(flags, ConstantPoolCacheEntry::tos_state_shift); // 将其他位全部丢弃。
// Make sure we don't need to mask flags after the above shift
ConstantPoolCacheEntry::verify_tos_state_shift();
// load return address
{
const address table_addr = (address) Interpreter::invoke_return_entry_table_for(code);
ExternalAddress table(table_addr); // 大致明白了,ExternalAddress就是虚拟机程序静态段的数据。
LP64_ONLY(__ lea(rscratch1, table));
LP64_ONLY(__ movptr(flags, Address(rscratch1, flags, Address::times_ptr))); // 根据返回类型获得返回例程。
NOT_LP64(__ movptr(flags, ArrayAddress(table, Address(noreg, flags, Address::times_ptr))));
}
// 让栈布局和从call_stub中进入是一样的。
// 在此处放入了返回地址,返回到一个invoke_return_entry中。
// push return address
__ push(flags);
// Restore flags value from the constant pool cache, and restore rsi
// for later null checks. r13 is the bytecode pointer
if (save_flags) {
// 恢复上面的保存操作。
__ movl(flags, rbcp);
__ restore_bcp();
}
// 结束上面所有的指令之后栈的状态:
// arg-1, ..., arg-n, ret-addr
// -> 增长方向。
}
27
:将当前的bcp保存到了栈中对应位置(上图bcp),在返回的时候会使用,需要注意的是保存的不是下一条指令的bcp,而是当前指令的bcp。
30
:如果知道ConstantPoolCache并不难理解,关于这部分的内容可以看其他内容。
33~47
:直接跳过,只关注invokevirtual
。
51~59
:获取receiver,跳进argument_address
看看就知道发生了什么。
71~77
:这个地方很重要,此时flags中放的是返回类型,然后根据返回类型从invoke_return_entry_table中获取对应的返回例程,将这个返回例程放入flags寄存器。
那么开始下一段代码,不过先记住当前栈和寄存器的状态:
// rbx: index(vtable的index)
// rcx: receiver
// rdx: flags
void TemplateTable::invokevirtual_helper(Register index,
Register recv,
Register flags) {
// Uses temporary registers rax, rdx
assert_different_registers(index, recv, rax, rdx); // index和recv需要保存,flags不用。
assert(index == rbx, "");
assert(recv == rcx, "");
// Test for an invoke of a final method
Label notFinal;
__ movl(rax, flags);
__ andl(rax, (1 << ConstantPoolCacheEntry::is_vfinal_shift));
__ jcc(Assembler::zero, notFinal);
const Register method = index; // method must be rbx
assert(method == rbx,
"Method* must be rbx for interpreter calling convention");
// do the call - the index is actually the method to call
// that is, f2 is a vtable index if !is_vfinal, else f2 is a Method*
// It's final, need a null check here!
__ null_check(recv);
// profile this call
__ profile_final_call(rax);
__ profile_arguments_type(rax, method, rbcp, true);
__ jump_from_interpreted(method, rax);
__ bind(notFinal);
// get receiver klass
__ null_check(recv, oopDesc::klass_offset_in_bytes());
__ load_klass(rax, recv);
// profile this call
__ profile_virtual_call(rax, rlocals, rdx);
// get target Method* & entry point
__ lookup_virtual_method(rax, index, method); // 将method指针放在method寄存器。
__ profile_called_method(method, rdx, rbcp);
__ profile_arguments_type(rdx, method, rbcp, true);
__ jump_from_interpreted(method, rdx);
}
忽略掉profile开头的一些函数,其他的部分都非常直观,就是从vtable中获取方法的指针,最终会跳入jump_from_interpreted
生成的代码中:
// Jump to from_interpreted entry of a call unless single stepping is possible
// in this thread in which case we must call the i2i entry
void InterpreterMacroAssembler::jump_from_interpreted(Register method, Register temp) {
prepare_to_jump_from_interpreted();
if (JvmtiExport::can_post_interpreter_events()) {
Label run_compiled_code;
// JVMTI events, such as single-stepping, are implemented partly by avoiding running
// compiled code in threads for which the event is enabled. Check here for
// interp_only_mode if these events CAN be enabled.
// interp_only is an int, on little endian it is sufficient to test the byte only
// Is a cmpl faster?
LP64_ONLY(temp = r15_thread;)
NOT_LP64(get_thread(temp);)
cmpb(Address(temp, JavaThread::interp_only_mode_offset()), 0); // 获取参数。
jccb(Assembler::zero, run_compiled_code);
jmp(Address(method, Method::interpreter_entry_offset())); // 如果只解释执行就只会获取_i2i_entry。
bind(run_compiled_code);
}
jmp(Address(method, Method::from_interpreted_offset())); // 获取_from_interpreted_entry,可能是个i2c_adapter。
// 解释模式下跳转到generate_normal_entry生成的代码。
}
和Jvmti有关的部分我也看不懂,不过不影响大体的逻辑,就是从method中获得了入口,然后跳转到入口(由TemplateInterpreterGenerator::generate_normal_entry
生成的),不过还是要注意一下prepare_to_jump_from_interpreted
这个函数,里面的逻辑很简单就是将sender_sp放在了_bcp_register寄存器中,同时将当前栈帧中(caller)的last_sp进行更新:
void InterpreterMacroAssembler::prepare_to_jump_from_interpreted() {
// 之前找不到generate_normal_entry中的sender rsp如何被设置的,这里发现了。
// set sender sp
lea(_bcp_register, Address(rsp, wordSize));
// record last_sp
movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), _bcp_register);
}
执行完毕后caller栈的情况是这样的:
解释执行的方法的通用入口由TemplateInterpreterGenerator::generate_normal_entry
生成,为了更加简洁一些我删掉了一些代码,被删除的代码和synchronized和profile有关:
// 通用的解释器方法入口,解释器进行调用解释器函数时会使用这个作为入口。
// 所以它会构造一个java方法栈。
//
// Generic interpreted method entry to (asm) interpreter
//
address TemplateInterpreterGenerator::generate_normal_entry(bool synchronized) {
// 执行下面生成代码时栈状态:
// arg1, ..., argn, ret-addr
// -> 增长方向。
// ebx: Method*
// rbcp: 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
// rbcp: 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
// 此时rdx中为非参数的locals。
// get return address
__ pop(rax); // 将ret-addr弹出。
// rlocals在这个过程中不会使用,但是确实一个约定的寄存器,用来保存locals的开始位置(从arg1开始)。
// compute beginning of parameters
__ lea(rlocals, Address(rsp, rcx, Interpreter::stackElementScale(), -wordSize));
// 创建非参数的locals。
// 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((int) NULL_WORD); // initialize local variables
__ decrementl(rdx); // until everything initialized
__ jcc(Assembler::greater, loop);
__ bind(exit);
}
// initialize fixed part of activation frame
generate_fixed_frame(false);
// step=0就是将bcp指向的字节码装载到rbx。
__ dispatch_next(vtos);
return entry_point;
}
31
:计算需要多少额外的local slot,java的locals表由两个部分组成,一个部分是传入的参数的slot,另外一部分就是需要额外生成的。
35
:将返回地址暂时弹出,因为locals是连续的,中间不应该有东西分隔。
39
:设置了locals指针。
45~54
:为额外的local slot开辟空间。
57
:生成固定部分,固定部分入图1所示。
61
:跳转到目标方法的字节码起始处,开始执行,在TemplateInterpreterGenerator::generate_fixed_frame
重新设置了rbcp指针。
结合图1TemplateInterpreterGenerator::generate_fixed_frame
的逻辑十分直观,这里就不说了。
返回
在返回之前,解释器会执行ireturn
字节码,假设方法返回一个int
类型的返回值。来看看生成ireturn
模板代码的函数,删掉了一些和安全点有关的代码:
void TemplateTable::_return(TosState state) {
transition(state, state);
// Narrow result if state is itos but result type is smaller.
// Need to narrow in the return bytecode rather than in generate_return_entry
// since compiled code callers expect the result to already be narrowed.
if (state == itos) {
__ narrow(rax);
}
__ remove_activation(state, rbcp);
// 回到callee栈了,rsp指向最后一个参数。
__ jmp(rbcp);
}
这里的逻辑非常简单,主要的处理其实在InterpreterMacroAssembler::remove_activation
里面,调用这个函数,将栈的状态恢复,将返回地址放入rbcp,返回值在(tos)rax,如果是32位则是eax/edx。
它是一个很长的函数,但是我们删掉monitor、Jvmti、溢出保留页这些逻辑之后整个函数变得特别简单:
// remove activation
void InterpreterMacroAssembler::remove_activation(
TosState state,
Register ret_addr,
bool throw_monitor_exception,
bool install_monitor_exception,
bool notify_jvmdi) {
// 最简单的情况下的执行路径非常简单,就是获取sender rsp,然后设置回去就行了。
// rax, rdx: Might contain return value
// remove activation
// get sender sp
movptr(rbx,
Address(rbp, frame::interpreter_frame_sender_sp_offset * wordSize));
leave(); // remove frame anchor
pop(ret_addr); // get return address
mov(rsp, rbx); // set sp to sender sp
}
这个函数生成的代码执行结束之后栈的状态如下:
我们之前设置了返回点,还记得么?就在TemplateTable::prepare_invoke
中,返回例程是在TemplateInterpreterGenerator::generate_return_entry_for
中生成的:
// 调用返回例程,会自动获取下一条指令。
address TemplateInterpreterGenerator::generate_return_entry_for(TosState state, int step, size_t index_size) {
address entry = __ pc();
// Restore stack bottom in case i2c adjusted stack
__ movptr(rsp, Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize));
// and NULL it as marker that esp is now tos until next java call
__ movptr(Address(rbp, frame::interpreter_frame_last_sp_offset * wordSize), (int32_t)NULL_WORD);
// 恢复约定的寄存器。
__ restore_bcp();
__ restore_locals();
const Register cache = rbx;
const Register index = rcx;
__ get_cache_and_index_at_bcp(cache, index, 1, index_size);
// 参数全部出栈。
const Register flags = cache;
__ movl(flags, Address(cache, index, Address::times_ptr, ConstantPoolCache::base_offset() + ConstantPoolCacheEntry::flags_offset()));
__ andl(flags, ConstantPoolCacheEntry::parameter_size_mask);
__ lea(rsp, Address(rsp, flags, Interpreter::stackElementScale()));
__ dispatch_next(state, step);
return entry;
}
6
:再次执行了一次恢复rsp。
8~12
:将寄存器恢复到之前的状态。
19~22
:出栈全部参数。
24
:跳转到下一条指令。
至此,调用完成,如果有返回值,栈顶缓存(tos)保存了返回值。
编译方法到编译方法
解释方法到编译方法
从解释方法到编译方法以及从编译方法
到解释方法,事实上都会使用adapter,也就是适配器。
Method对象有3个入口:
- _i2i_entry(从解释器到解释器入口)
- _from_compiled_entry(从已编译代码进入此方法的入口)
- _from_interpreted_entry(从解释器代码进入此方法的入口)
第三个_from_interpreted_entry
一开始和_i2i_entry
指向同一段代码,但是在方法被编译之后就会指向
而_i2i_entry
是固定的,并不会随着
// get_adapter0