通过《JVM内存池》,我们已经可以顺利的创建出对象?具体怎么使用。
Java Main方法调用
自动生成stub
Threads::create_vm()《openJdk的启动流程》中的方法
init_globals()–>
stubRoutines_init1()
StubRoutines::initialize1
StubRoutines::StubGenerator_generate #根据cpu体系不同会选择不同实现加载
StubGenerator::generate_initial
StubGenerator::generate_call_stub
JavaMain《openJdk的启动流程》中的方法
jni_CallStaticVoidMethod
jni_invoke_static
JavaCalls::call
JavaCalls::call_helper
StubRoutines::call_stub
//hotspot/src/share/vm/prims/jni.cpp
JNI_ENTRY(void, jni_CallStaticVoidMethod(JNIEnv *env, jclass cls, jmethodID methodID, ...))
JNIWrapper("CallStaticVoidMethod");
#ifndef USDT2
DTRACE_PROBE3(hotspot_jni, CallStaticVoidMethod__entry, env, cls, methodID);
#else /* USDT2 */
HOTSPOT_JNI_CALLSTATICVOIDMETHOD_ENTRY(
env, cls, (uintptr_t) methodID);
#endif /* USDT2 */
DT_VOID_RETURN_MARK(CallStaticVoidMethod);
va_list args;
va_start(args, methodID);
//c++隐式创建对象直接分配在栈中,定义结果类型
JavaValue jvalue(T_VOID);
//c++隐式创建对象直接分配在栈中,定义参数
JNI_ArgumentPusherVaArg ap(methodID, args);
//开始执行
jni_invoke_static(env, &jvalue, NULL, JNI_STATIC, methodID, &ap, CHECK);
va_end(args);
JNI_END
static void jni_invoke_nonstatic(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type, jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
//校验调用实例方法关联的对象receiver不能为空
oop recv = JNIHandles::resolve(receiver);
if (recv == NULL) {
THROW(vmSymbols::java_lang_NullPointerException());
}
Handle h_recv(THREAD, recv);
int number_of_parameters;
Method* selected_method;
{
//将jmethodID转换成Method*
Method* m = Method::resolve_jmethod_id(method_id);
//获取方法参数个数
number_of_parameters = m->size_of_parameters();
//获取此方法所属的类Klass
Klass* holder = m->method_holder();
if (call_type != JNI_VIRTUAL) {
//如果是非虚方法调用,即CallNonvirtual<type>Method,则使用指定的方法
//此时jmethodID从父类Klass获取的则使用父类的实现,如果使用子类Klass的实现则使用子类的实现
selected_method = m;
}
//虚方法调用,即Call<type>Method,itable即接口方法表
else if (!m->has_itable_index()) {
// non-interface call -- for that little speed boost, don't handlize
// 非接口方法调用
debug_only(No_Safepoint_Verifier nosafepoint;)
//校验该方法在虚方法表的索引是否有效,如果目标类已经完成链接和初始化则valid_vtable_index()方法返回true
assert(m->valid_vtable_index(), "no valid vtable index");
//获取虚方法表中的索引,注意同一个方法,无论从子类Klass获取还是从父类Klass获取,其vtbl_index都是一样的
int vtbl_index = m->vtable_index();
//如果vtbl_index不等于nonvirtual_vtable_index,nonvirtual_vtable_index表示该方法不需要通过vtable分发,即父类定义的final方法
if (vtbl_index != Method::nonvirtual_vtable_index) {
//获取receiver对应的Klass
Klass* k = h_recv->klass();
InstanceKlass *ik = (InstanceKlass*)k;
//获取目标Klass在指定虚方法表索引处的虚方法实现,
//如receiver实际是子类实例,jmethodID无论从父类Klass还是子类Klass获取的,实际调用的都是子类的实现
selected_method = ik->method_at_vtable(vtbl_index);
} else {
//final方法
selected_method = m;
}
} else {
//接口方法
KlassHandle h_holder(THREAD, holder);
//获取接口方法表中的索引,无论jmethodID从接口类Klass还是实现类Klass获取的,其itbl_index都是一样的
int itbl_index = m->itable_index();
Klass* k = h_recv->klass();
//获取接口方法,使用receiver实例实际的类的接口实现
selected_method = InstanceKlass::cast(k)->method_at_itable(h_holder(), itbl_index, CHECK);
}
}
methodHandle method(THREAD, selected_method);
ResourceMark rm(THREAD);
//c++隐式创建对象直接分配在栈中
JavaCallArguments java_args(number_of_parameters);
args->set_java_argument_object(&java_args);
//校验方法不是静态方法
assert(!method->is_static(), "method should not be static");
//设置接受方法调用的对象实例
args->push_receiver(h_recv); // Push jobject handle
//解析方法参数
args->iterate( Fingerprinter(method).fingerprint() );
//设置方法返回类型
result->set_type(args->get_ret_type());
//调用方法
JavaCalls::call(result, method, &java_args, CHECK);
//处理结果返回值
if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
}
}
JavaCalls
JavaCalls是本地代码执行Java方法调用的一个工具类,会创建一个新的栈帧,做必要的栈帧切换工作,保证新的栈帧与原有的栈帧被正确的链接起来。
//hotspot/src/share/vm/runtime/javaCalls.hpp
class JavaCalls : AllStatic{
//JavaCallArguments,Java方法调用参数,空间由栈提供
//JavaValue,任何类型的Java变量的容器,用来描述返回值,空间由栈提供
//methodHandle,可以直接执行的对普通方法,构造方法,字段等操作或者其他的低级别的操作的引用,空间由栈提供
void JavaCalls::call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS) {
//校验当前线程是否是Java线程,即不能本地C++线程
assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
//一个钩子方法,可以执行平台相关的特殊处理逻辑
//最终调用的还是call_helper方法
os::os_exception_wrapper(call_helper, result, &method, args, THREAD);
}
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
//执行dump是不能执行java字节码
assert(!DumpSharedSpaces, "must not execute Java bytecodes when dumping");
methodHandle method = *m;
JavaThread* thread = (JavaThread*)THREAD;
//校验当前线程是Java线程
assert(thread->is_Java_thread(), "must be called by a java thread");
//校验方法不为空
assert(method.not_null(), "must have a method to call");
//校验当前没有在安全点
assert(!SafepointSynchronize::is_at_safepoint(), "call to Java code during VM operation");
//校验当前线程的handle_area没有执行handle_mark,即GC标记
assert(!thread->handle_area()->no_handle_mark_active(), "cannot call out to Java here");
CHECK_UNHANDLED_OOPS_ONLY(thread->clear_unhandled_oops();)
//如果需要检查JNI方法调用的参数
if (CheckJNICalls) {
args->verify(method, result->get_type());
}
else debug_only(args->verify(method, result->get_type()));
//方法为空实现
if (method->is_empty_method()) {
//校验方法的返回类型是否是void
assert(result->get_type() == T_VOID, "an empty method must return a void value");
return;
}
//校验当前线程是否是JIT编译线程
assert(!thread->is_Compiler_thread(), "cannot compile from the compiler");
//如果必须编译此方法
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);
//判断返回值是否是oop或者数组
bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);
//获取保存方法调用结果的指针
intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
//获取方法调用的接受对象,如果是非静态方法则从args中获取,否则用一个空的Handle实例代替
Handle receiver = (!method->is_static()) ? args->receiver() : Handle();
//校验当前线程是否处于调用栈overflow,如果是则需要抛出异常
if (thread->stack_yellow_zone_disabled()) {
thread->reguard_stack();
}
//判断当前线程的调用栈是否有足够的内存
if (!os::stack_shadow_pages_available(THREAD, method)) {
//内存不足,抛出stack_overflow异常
Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method);
return;
} else {
//占用足够的内存
os::bang_stack_shadow_pages();
}
//每次执行Java方法调用时都需要创建一个新的JavaCallWrapper实例,然后在方法调用结束销毁这个实例,通过JavaCallWrapper实例的创建和销毁来保存方法调用前当前线程的上一个栈帧,重新分配或者销毁一个handle block,保存和重置Java调用栈的fp/sp
{ JavaCallWrapper link(method, receiver, result, CHECK);
{ HandleMark hm(thread);
//StubRoutines::call_stub()返回一函数指针
//后面带上参数调用
StubRoutines::call_stub()(
(address)&link,
result_val_address,
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);
//处理调用结果
result = link.result();
//将调用结果保存到当前线程的vm_result属性,保护oop
if (oop_result_flag) {
thread->set_vm_result((oop) result->get_jobject());
}
}
} // Exit JavaCallWrapper
if (oop_result_flag) {
//将方法调用结果放到JavaValue中
result->set_jobject((jobject)thread->vm_result());
thread->set_vm_result(NULL);
}
}
}
StubRoutines
StubRoutines是一个包含一系列编译程序或者JVM运行时系统使用的关键函数的地址的Holder类,通过StubRoutines获取这些函数的内存地址,然后通过指针的方式调用目标函数
//hotspot src/share/vm/runtime/stubRoutines.hpp
class StubRoutines: AllStatic{
//返回_call_stub_entry地址的函数
//_call_stub_entry为初始化时,根据不同CPU体系布设置
static CallStub call_stub() {
return CAST_TO_FN_PTR(CallStub, _call_stub_entry);
}
}
StubGenerator
StubGenerator顾名思义就是用来生成Stub的,这里的Stub实际是一段可执行的汇编代码,具体来说就是生成StubRoutines中定义的多个public static的函数调用点,调用方可以将其作为一个经过优化后的函数直接使用,存在不同CPU架构的实现,我们重点关注x86_64的实现。
//hotspot/src/cpu/x86/vm/stubGenerator_x86_64.cpp
class StubGenerator :(继承StackObj) {
void generate_initial() {
...
//通过generate_call_stub设置了我们的函数
StubRoutines::_call_stub_entry = generate_call_stub(StubRoutines::_call_stub_return_address);
...
}
address generate_call_stub(address& return_address) {
//rsp_after_call_off和call_wrapper_off都是定义的枚举,表示对应项相对于rbp的偏移字节数
//比如call_wrapper_off就是JavaCallWrapper实例相对于rbp的偏移字节数
//这里是校验栈帧的属性和当前系统的属性是否一致
assert((int)frame::entry_frame_after_call_words == -(int)rsp_after_call_off + 1 &&
(int)frame::entry_frame_call_wrapper_offset == (int)call_wrapper_off,
"adjust this code");
StubCodeMark mark(this, "StubRoutines", "call_stub");
//获取写入汇编代码的内存地址
address start = __ pc();
//根据各项的偏移量计算各项的存储位置
const Address rsp_after_call(rbp, rsp_after_call_off * wordSize);
const Address call_wrapper (rbp, call_wrapper_off * wordSize);
const Address result (rbp, result_off * wordSize);
const Address result_type (rbp, result_type_off * wordSize);
const Address method (rbp, method_off * wordSize);
const Address entry_point (rbp, entry_point_off * wordSize); //entry_point就是解释器的调用入口
const Address parameters (rbp, parameters_off * wordSize);
const Address parameter_size(rbp, parameter_size_off * wordSize);
// same as in generate_catch_exception()!
const Address thread (rbp, thread_off * wordSize);
const Address r15_save(rbp, r15_off * wordSize);
const Address r14_save(rbp, r14_off * wordSize);
const Address r13_save(rbp, r13_off * wordSize);
const Address r12_save(rbp, r12_off * wordSize);
const Address rbx_save(rbp, rbx_off * wordSize);
// stub code
//enterf方法是保存rbp寄存器到栈中,然后把rsp中的值拷贝到rbp中
__ enter();
//sub指令是减去特定值,这里是将rsp往低地址方向移动指定的偏移量,至此一个新的栈帧展开了
__ subptr(rsp, -rsp_after_call_off * wordSize);
// 即不是WIN64系统
#ifndef _WIN64
//将c_rarg5即r9寄存器的值拷贝到parameters地址上
__ movptr(parameters, c_rarg5); // parameters
//将c_rarg4即r8寄存器的值拷贝到entry_point地址上
__ movptr(entry_point, c_rarg4); // entry_point
#endif
//同上,c_rarg3对应rcx寄存器
__ movptr(method, c_rarg3); // method
//c_rarg2对应rdx寄存器
__ movl(result_type, c_rarg2); // result type
//c_rarg1对应rsi寄存器
__ movptr(result, c_rarg1); // result
//c_rarg0对应rdi寄存器
__ movptr(call_wrapper, c_rarg0); // call wrapper
// 将下列寄存器的值复制到对应的地址上
__ movptr(rbx_save, rbx);
__ movptr(r12_save, r12);
__ movptr(r13_save, r13);
__ movptr(r14_save, r14);
__ movptr(r15_save, r15);
#ifdef _WIN64
for (int i = 6; i <= 15; i++) {
__ movdqu(xmm_save(i), as_XMMRegister(i));
}
const Address rdi_save(rbp, rdi_off * wordSize);
const Address rsi_save(rbp, rsi_off * wordSize);
__ movptr(rsi_save, rsi);
__ movptr(rdi_save, rdi);
#else
const Address mxcsr_save(rbp, mxcsr_off * wordSize);
{
Label skip_ldmx;
__ stmxcsr(mxcsr_save);
__ movl(rax, mxcsr_save);
__ andl(rax, MXCSR_MASK); // Only check control and mask bits
ExternalAddress mxcsr_std(StubRoutines::addr_mxcsr_std());
__ cmp32(rax, mxcsr_std);
__ jcc(Assembler::equal, skip_ldmx);
__ ldmxcsr(mxcsr_std);
__ bind(skip_ldmx);
}
#endif
// Load up thread register
//将r15_thread即r15寄存器中的数值拷贝到thread处的栈中
__ movptr(r15_thread, thread);
//heapbase的重新初始化话
__ reinit_heapbase();
// pass parameters if any
BLOCK_COMMENT("pass parameters if any");
Label parameters_done;
//parameter_size拷贝到c_rarg3即rcx寄存器中
__ movl(c_rarg3, parameter_size);
//校验c_rarg3的数值是否合法
__ testl(c_rarg3, c_rarg3);
//如果不合法则跳转到parameters_done分支上
__ jcc(Assembler::zero, parameters_done);
Label loop;‘=
//将地址parameters包含的数据即参数对象的指针拷贝到c_rarg2寄存器中
__ movptr(c_rarg2, parameters); // parameter pointer
//将c_rarg3中值拷贝到c_rarg1中,即将参数个数复制到c_rarg1中
__ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1
//打标
__ BIND(loop);
//将c_rarg2指向的内存中包含的地址复制到rax中
__ movptr(rax, Address(c_rarg2, 0));// get parameter
//c_rarg2中的参数对象的指针加上指针宽度8字节,即指向下一个参数
__ addptr(c_rarg2, wordSize); // advance to next parameter
//将c_rarg1中的值减一
__ decrementl(c_rarg1); // decrement counter
//传递方法调用参数
__ push(rax); // pass parameter
//如果参数个数大于0则跳转到loop继续
__ jcc(Assembler::notZero, loop);
// 打标
__ BIND(parameters_done);
//将method地址包含的数据接Method*拷贝到rbx中
__ movptr(rbx, method); // get Method*
//将解释器的入口地址拷贝到c_rarg1寄存器中
__ movptr(c_rarg1, entry_point); // get entry_point
//将rsp寄存器的数据拷贝到r13寄存器中
__ mov(r13, rsp); // set sender sp
BLOCK_COMMENT("call Java function");
//调用解释器的解释函数,从而调用Java方法
__ call(c_rarg1);
BLOCK_COMMENT("call_stub_return_address:");
//获取此时的汇编代码写入位置,
return_address = __ pc();
//保存方法调用结果依赖于结果类型,只要不是T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE,都当做INT处理
//将result地址的值拷贝到c_rarg0中
__ movptr(c_rarg0, result);
Label is_long, is_float, is_double, exit;
//将result_type地址的值拷贝到c_rarg1
__ movl(c_rarg1, result_type);
//根据结果类型的不同跳转到不同的处理分支
__ cmpl(c_rarg1, T_OBJECT);
__ jcc(Assembler::equal, is_long);
__ cmpl(c_rarg1, T_LONG);
__ jcc(Assembler::equal, is_long);
__ cmpl(c_rarg1, T_FLOAT);
__ jcc(Assembler::equal, is_float);
__ cmpl(c_rarg1, T_DOUBLE);
__ jcc(Assembler::equal, is_double);
// 处理结果类型是int的情形,将rax中的值写入c_rarg0对应地址的内存中
__ movl(Address(c_rarg0, 0), rax);
//打标
__ BIND(exit);
// rsp_after_call的有效地址拷贝到rsp中,即将rsp往高地址方向移动了,原来的方法调用参数相当于pop掉了
__ lea(rsp, rsp_after_call);
// restore regs belonging to calling function
#ifdef _WIN64
for (int i = 15; i >= 6; i--) {
__ movdqu(as_XMMRegister(i), xmm_save(i));
}
#endif
//恢复其他寄存器的值,即恢复方法调用前的现场
__ movptr(r15, r15_save);
__ movptr(r14, r14_save);
__ movptr(r13, r13_save);
__ movptr(r12, r12_save);
__ movptr(rbx, rbx_save);
#ifdef _WIN64
__ movptr(rdi, rdi_save);
__ movptr(rsi, rsi_save);
#else
__ ldmxcsr(mxcsr_save);
#endif
// 恢复rsp
__ addptr(rsp, -rsp_after_call_off * wordSize);
// 恢复rbp
__ pop(rbp);
//退出
__ ret(0);
// handle return types different from T_INT
__ BIND(is_long);
//调用不同的指令将rax中的值写入c_rarg0的地址对应的内存中
__ movq(Address(c_rarg0, 0), rax);
__ jmp(exit);
__ BIND(is_float);
__ movflt(Address(c_rarg0, 0), xmm0);
__ jmp(exit);
__ BIND(is_double);
__ movdbl(Address(c_rarg0, 0), xmm0);
__ jmp(exit);
return start;
}
}
执行Java方法调用的整体思路:
- 先将用来传递参数的所有寄存器的值拷贝到栈帧中
- 再从栈帧中将必要的方法调用参数,参数个数,Method*等复制到寄存器中
- 然后执行方法调用,调用结束再从栈帧中将参数寄存器中的值恢复至执行方法调用前的状态,在恢复rsp,rbp等就可以正常退出调用了。
//---------c++隐式创建对象直接分配在栈中--------//
//java参数
JavaCallArguments java_args(number_of_parameters);
//方法返回值
JavaValue jvalue(T_VOID);
//方法的引用
methodHandle method(THREAD, selected_method);
//&link表示调用方调用此方法的地址(call指令所在的地址)
JavaCallWrapper link(method, receiver, result, CHECK);
//---------c++隐式创建对象直接分配在栈中--------//
// Linux Arguments:
// c_rarg0: call wrapper address address
// c_rarg1: result address
// c_rarg2: result type BasicType
// c_rarg3: method Method*
// c_rarg4: (interpreter) entry point address
// c_rarg5: parameters intptr_t*
// 16(rbp): parameter size (in words) int
// 24(rbp): thread Thread*
// 使用前面参数直接c_rarg0-5寄存器,后面通过rbp传值
StubRoutines::call_stub()(
(address)&link, //方法执行完后回来的地址
result_val_address,//结果存放地址
result_type,//结果类型
method(),//方法地址
entry_point,//解释器的调用入口
args->parameters(),//参数
args->size_of_parameters(),//参数个数
CHECK
);
// 使用 -XX:+UnlockDiagnosticVMOptions -XX:+PrintStubCode
// hsdis-amd64.dylib 插件下载地址为 https://github.com/evolvedmicrobe/benchmarks
// generate_call_stub 生成的汇编码如下
StubRoutines::call_stub [0x000000011b2e145f, 0x000000011b2e1547[ (232 bytes)
0x000000011b2e145f: push %rbp //保存上一桢的
0x000000011b2e1460: mov %rsp,%rbp
// 0 [ saved rbp ] <--- rbp rsp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
0x000000011b2e1463: sub $0x60,%rsp
0x000000011b2e1467: mov %r9,-0x8(%rbp)
0x000000011b2e146b: mov %r8,-0x10(%rbp)
0x000000011b2e146f: mov %rcx,-0x18(%rbp)
0x000000011b2e1473: mov %edx,-0x20(%rbp)
0x000000011b2e1476: mov %rsi,-0x28(%rbp)
0x000000011b2e147a: mov %rdi,-0x30(%rbp)
0x000000011b2e147e: mov %rbx,-0x38(%rbp)
0x000000011b2e1482: mov %r12,-0x40(%rbp)
0x000000011b2e1486: mov %r13,-0x48(%rbp)
0x000000011b2e148a: mov %r14,-0x50(%rbp)
0x000000011b2e148e: mov %r15,-0x58(%rbp)
// -11 [ saved r15 ] <--- rsp rsp_after_call
// -10 [ saved r14 ]
// -9 [ saved r13 ]
// -8 [ saved r12 ]
// -7 [ saved rbx ]
// -6 [ call wrapper ]
// -5 [ result ]
// -4 [ result type ]
// -3 [ method ]
// -2 [ entry point ]
// -1 [ parameters ]
// 0 [ saved rbp ] <--- rbp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
0x000000011b2e1492: stmxcsr -0x60(%rbp)
0x000000011b2e1496: mov -0x60(%rbp),%eax
0x000000011b2e1499: and $0xffc0,%eax
0x000000011b2e149f: cmp -0xdce07e5(%rip),%eax # 0x000000010d600cc0
0x000000011b2e14a5: je 0x000000011b2e14b2
0x000000011b2e14ab: ldmxcsr -0xdce07f2(%rip) # 0x000000010d600cc0
0x000000011b2e14b2: mov 0x18(%rbp),%r15
0x000000011b2e14b6: mov -0xdccb7c5(%rip),%r12 # 0x000000010d615cf8
0x000000011b2e14bd: mov 0x10(%rbp),%ecx
0x000000011b2e14c0: test %ecx,%ecx
0x000000011b2e14c2: je 0x000000011b2e14da
0x000000011b2e14c8: mov -0x8(%rbp),%rdx
0x000000011b2e14cc: mov %ecx,%esi
0x000000011b2e14ce: mov (%rdx),%rax
0x000000011b2e14d1: add $0x8,%rdx
0x000000011b2e14d5: dec %esi
0x000000011b2e14d7: push %rax
0x000000011b2e14d8: jne 0x000000011b2e14ce
0x000000011b2e14da: mov -0x18(%rbp),%rbx
0x000000011b2e14de: mov -0x10(%rbp),%rsi
0x000000011b2e14e2: mov %rsp,%r13
// [ argument word n ]<--- rsp
// ...
// -12 [ argument word 1 ]
// -11 [ saved r15 ] <--- rsp_after_call
// -10 [ saved r14 ]
// -9 [ saved r13 ]
// -8 [ saved r12 ]
// -7 [ saved rbx ]
// -6 [ call wrapper ]
// -5 [ result ]
// -4 [ result type ]
// -3 [ method ]
// -2 [ entry point ]
// -1 [ parameters ]
// 0 [ saved rbp ] <--- rbp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
0x000000011b2e14e5: callq *%rsi
//CALL指令效果是将返回地址入栈,并跳转到调用过程的起始处。
// [ return_from_Java ]<--- rsp
// [ argument word n ]
// ...
// -12 [ argument word 1 ]
// -11 [ saved r15 ] <--- rsp_after_call
// -10 [ saved r14 ]
// -9 [ saved r13 ]
// -8 [ saved r12 ]
// -7 [ saved rbx ]
// -6 [ call wrapper ]
// -5 [ result ]
// -4 [ result type ]
// -3 [ method ]
// -2 [ entry point ]
// -1 [ parameters ]
// 0 [ saved rbp ] <--- rbp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
0x000000011b2e14e7: mov -0x28(%rbp),%rdi //result
0x000000011b2e14eb: mov -0x20(%rbp),%esi //result_type
0x000000011b2e14ee: cmp $0xc,%esi
0x000000011b2e14f1: je 0x000000011b2e1536
0x000000011b2e14f7: cmp $0xb,%esi
0x000000011b2e14fa: je 0x000000011b2e1536
0x000000011b2e1500: cmp $0x6,%esi
0x000000011b2e1503: je 0x000000011b2e153b
0x000000011b2e1509: cmp $0x7,%esi
0x000000011b2e150c: je 0x000000011b2e1541
//%eax是方法调用后的结果值
//通过result_type判断后,然后将值写到result中
0x000000011b2e1512: mov %eax,(%rdi)
// rsp_after_call的有效地址拷贝到rsp中,即将rsp往高地址方向移动了,原来的方法调用参数相当于pop掉了
0x000000011b2e1514: lea -0x60(%rbp),%rsp
// -11 [ saved r15 ] <--- rsp rsp_after_call
// -10 [ saved r14 ]
// -9 [ saved r13 ]
// -8 [ saved r12 ]
// -7 [ saved rbx ]
// -6 [ call wrapper ]
// -5 [ result ]
// -4 [ result type ]
// -3 [ method ]
// -2 [ entry point ]
// -1 [ parameters ]
// 0 [ saved rbp ] <--- rbp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
//恢复其他寄存器的值,即恢复方法调用前的现场
0x000000011b2e1518: mov -0x58(%rbp),%r15
0x000000011b2e151c: mov -0x50(%rbp),%r14
0x000000011b2e1520: mov -0x48(%rbp),%r13
0x000000011b2e1524: mov -0x40(%rbp),%r12
0x000000011b2e1528: mov -0x38(%rbp),%rbx
0x000000011b2e152c: ldmxcsr -0x60(%rbp)
0x000000011b2e1530: add $0x60,%rsp
// 0 [ saved rbp ] <--- rbp rsp
// 1 [ return address ]
// 2 [ parameter size ]
// 3 [ thread ]
0x000000011b2e1534: pop %rbp
//ret 指令用于从过程调用中返回,从栈中弱出地址,并跳转到那个位置
//弹出return address,并pc计数器跳转过去
//和pop %rbp,回归到之前的栈桢环境
0x000000011b2e1535: retq
0x000000011b2e1536: mov %rax,(%rdi)
0x000000011b2e1539: jmp 0x000000011b2e1514
0x000000011b2e153b: vmovss %xmm0,(%rdi)
0x000000011b2e153f: jmp 0x000000011b2e1514
0x000000011b2e1541: vmovsd %xmm0,(%rdi)
0x000000011b2e1545: jmp 0x000000011b2e1514
java桢栈
根据上文分析,call entry_point 是进行java方法的真正入口。
#entry_point 来自method对象中的方法中的一个方法
address entry_point = method->from_interpreted_entry();
//来自于method是的一属性_from_interpreted_entry
volatile address from_interpreted_entry() const{
return (address)OrderAccess::load_ptr_acquire(&_from_interpreted_entry);
}
//在方法链接过程中发现其最终来源解释器Interpreter
void Method::link_method(const methodHandle& h_method, TRAPS) {
...
if (!is_shared()) {
// entry_for_method会找到刚刚generate_normal_entry设置的入口点
address entry = Interpreter::entry_for_method(h_method);
// 将它设置为解释器入口点,即可_i2i_entry和_from_interpreted_entry
set_interpreter_entry(entry);
}
...
// 设置_from_compiled_entry的适配器
(void) make_adapters(h_method, CHECK);
}
有关于解析器的请参考《hotspot解释器和JIT》
最终call entry_point 会调用生成的方法
Threads::create_vm()
init_globals()
interpreter_init()()
emplateInterpreter::initialize()
TemplateInterpreterGenerator() // 构造函数
TemplateInterpreterGenerator::generate_all()
TemplateInterpreterGenerator::generate_normal_entry()
// 此时的栈,stack
// [ argument word n ]<--- rsp
// ...
// [ argument word 1 ]
// [ 之前的省略,请参考上文 ]
address InterpreterGenerator::generate_normal_entry(bool synchronized) {
//UseCompiler表示使用JIT编译器,默认为true
//CountCompiledCalls表示统计编译方法的执行次数
//inc_counter表示是否增加方法调用计数
bool inc_counter = UseCompiler || CountCompiledCalls;
//获取BufferBlob写入地址
address entry_point = __ pc();
//执行此段指令前,在call_stub中已经把待执行方法的Method*放入rbx中
//获取Method中constMethod属性的内存地址
const Address constMethod(rbx, Method::const_offset());
//获取Method中access_flags属性的内存地址
const Address access_flags(rbx, Method::access_flags_offset());
//rdx后面会被设置成constMethod的内存地址,这里是获取constMethod中的size_of_parameters属性的内存地址
const Address size_of_parameters(rdx,
ConstMethod::size_of_parameters_offset());
const Address size_of_locals(rdx, ConstMethod::size_of_locals_offset());
//将constMethod的内存地址放入rdx中
__ movptr(rdx, constMethod);
//将size_of_parameters地址处的方法参数个数读取到rcx中
__ load_unsigned_short(rcx, size_of_parameters);
//此时寄存器中的值
// rbx: Method*
// rcx: size of parameters
// r13: sender_sp,即执行此段指令前的rsp地址,在rsp下面就是方法调用的具体入参
//将size_of_locals地址处的此方法的本地变量个数读取到rdx中
__ load_unsigned_short(rdx, size_of_locals); // get size of locals in words
//将rdx中的本地变量个数减去方法参数个数
__ subl(rdx, rcx); // rdx = no. of additional locals
// 确保有足够的内存空间开始一个新的栈帧
generate_stack_overflow_check();
// 将栈顶的值放入rax中,栈顶的值就是此时rsp中的地址,即Java方法执行完成后的地址
__ pop(rax);
//执行此段指令时因为还未移动rsp,rbp,所以rsp的地址不变依然是执行此段指令前的rsp地址
//计算起始方法入参的地址,将其保存到r14中
__ lea(r14, Address(rsp, rcx, Address::times_8, -wordSize));
// rdx - # of additional locals
// allocate space for locals
// explicitly initialize locals
{
Label exit, loop;
//test指令执行逻辑与操作,结果保存到标志寄存器中,如果rdx小于0,则逻辑与的结果也是小于0
__ testl(rdx, rdx);
//如果rdx小于或者等于0,即本地变量个数小于方法参数个数,则不用做什么,跳转到exit
__ jcc(Assembler::lessEqual, exit); // do nothing if rdx <= 0
//如果rdx大于0
__ bind(loop);
//将0放入当前栈帧中,即完成本地变量的初始化
__ push((int) NULL_WORD); // initialize local variables
//让rdx减1
__ decrementl(rdx); // until everything initialized
//判断rdx是否大于0,如果大于则跳转到loop开始执行,即不断push rdx个0到栈帧中,将额外的本地变量都初始化掉
__ jcc(Assembler::greater, loop);
__ bind(exit);
}
// 此时的栈,stack
// [ 本地变量 word n ]<--- rsp
// ...
// [ 本地变量 word 1 ]
// [ argument word n ]
// ...
// [ argument word 1 ]
//初始化一个新的栈帧,获取并保存方法的字节码,ConstantPoolCache等的地址
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.
//r15_thread保存了当前线程Thread*的引用
//获取Thread的do_not_unlock_if_synchronized属性的地址
const Address do_not_unlock_if_synchronized(r15_thread,
in_bytes(JavaThread::do_not_unlock_if_synchronized_offset()));
//将o_not_unlock_if_synchronized属性置为true
__ movbool(do_not_unlock_if_synchronized, true);
//执行profile统计
__ profile_parameters_type(rax, rcx, rdx);
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);
// check for synchronized interpreted methods
bang_stack_shadow_pages(false);
//将当前线程的do_not_unlock_if_synchronized属性置为false
__ movbool(do_not_unlock_if_synchronized, false);
// check for synchronized methods
// Must happen AFTER invocation_counter check and stack overflow check,
// so method is not locked if overflows.
//如果需要上锁则分配monitor然后锁定此方法
if (synchronized) {
// Allocate monitor and lock method
lock_method();
} else {
// no synchronization necessary
}
// start execution
// 发布JVMTI事件
__ notify_method_entry();
//开始字节码执行
__ dispatch_next(vtos);
//方法执行完成
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);
//最终执行InterpreterRuntime::frequency_counter_overflow方法,这里会完成方法的编译
//如果编译完成,则跳转到continue_after_compile
generate_counter_overflow(&continue_after_compile);
}
return entry_point;
}
// Args:
// rax: return address
// rbx: Method*
// r14: pointer to locals
// r13: sender sp 老的桢地址
// rdx: cp cache
// 此时的栈,stack
// [ 本地变量 word n ]<--- rsp
// ...
// [ 本地变量 word 1 ]
// [ argument word n ]
// ...
// [ argument word 1 ]
void TemplateInterpreterGenerator::generate_fixed_frame(bool native_call) {
// initialize fixed part of activation frame
//保存rax中的java方法返回地址到栈帧中
__ push(rax); // save return address
//保存rbp,将rsp的值复制到rbp中
__ enter(); // save old & set new rbp
// [ save rbp ] <--- rbp rsp
// [ return address ]
// [ 本地变量 word n ]
// ...
// [ 本地变量 word 1 ]
// [ argument word n ]
// ...
// [ argument word 1 ]
//将 sender sp保存到栈帧中
__ push(r13); // set sender sp
//push一个0,用来保存last_sp
__ push((int)NULL_WORD); // leave last_sp as null
//将Method的ConstMethod属性的地址放到r13中
__ movptr(r13, Address(rbx, Method::const_offset())); // get ConstMethod*
//获取保存字节码的的内存地址,保存到r13中
__ lea(r13, Address(r13, ConstMethod::codes_offset())); // get codebase
//将Method*保存到rbx中
__ push(rbx); // save Method*
// [Method* ] <--- rsp
// [last sp ]
// [old stack pointer ]
// [ save rbp ] <--- rbp rsp
// [ return address ]
// [ 本地变量 word n ]
// ...
// [ 本地变量 word 1 ]
// [ argument word n ]
// ...
// [ argument word 1 ]
//如果统计解释器性能
if (ProfileInterpreter) {
Label method_data_continue;
__ movptr(rdx, Address(rbx, in_bytes(Method::method_data_offset())));
__ testptr(rdx, rdx);
__ jcc(Assembler::zero, method_data_continue);
__ addptr(rdx, in_bytes(MethodData::data_offset()));
__ bind(method_data_continue);
__ push(rdx); // set the methodData
} else {
__ push(0);
}
//获取Method的ConstMethod属性的地址
__ movptr(rdx, Address(rbx, Method::const_offset()));
//获取ConstMethod的ConstantPool地址
__ movptr(rdx, Address(rdx, ConstMethod::constants_offset()));
//获取ConstantPool的ConstantPoolCache的地址
__ movptr(rdx, Address(rdx, ConstantPool::cache_offset_in_bytes()));
//将ConstantPool的ConstantPoolCache的地址保存到栈帧中
__ push(rdx); // set constant pool cache
//将方法入参的地址,即本地变量表的起始地址放入栈帧中
__ push(r14); // set locals pointer
if (native_call) {
__ push(0); // no bcp
} else {
//将字节码的起始地址放入栈帧中
__ push(r13); // set bcp
}
//将0放入栈帧中,标识栈顶
__ push(0); // reserve word for pointer to expression stack bottom
//将rsp的地址放入栈帧中
__ movptr(Address(rsp, 0), rsp); // set expression stack bottom
}
// Layout of asm interpreter frame:
// [expression stack ] * <- sp
// [monitors ] \
// ... | monitor block size
// [monitors ] /
// [monitor block size ]
// [byte code index/pointr] = bcx() bcx_offset
// [pointer to locals ] = locals() locals_offset
// [constant pool cache ] = cache() cache_offset
// [methodData ] = mdp() mdx_offset
// [Method* ] = method() method_offset
// [last sp ] = last_sp() last_sp_offset
// [old stack pointer ] (sender_sp) sender_sp_offset
// [old frame pointer ] <- fp = link()
// [return pc ]
// [oop temp ] (only for native calls)
// [locals and parameters ]
// <- sender sp
给出一段代码,并用sa分析
public class MainTest {
public int compute(int a, int b){
a = a + 1;
b = b - 2;
int temp = a + b;
return temp;
}
public static void main(String[] args) {
MainTest t = new MainTest();
int a = 10;
int b = 20;
int s = t.compute(a, b);
System.out.println(s);
}
}
程序断点在a = a + 1;执行之前
图中一共描述了2个桢栈
0x0000700001590860: 0x000000010f48f2d7 //操作栈 expression stack
0x0000700001590868: 0x0000700001590868 //栈顶值 <--compute 栈顶
0x0000700001590870: 0x000000010dc4c310 //代码地址
0x0000700001590878: 0x00007000015908d0 //变量指针
0x0000700001590880: 0x000000010dc4c4b8 //常量池
0x0000700001590888: 0x0000000000000000 //methodData
0x0000700001590890: 0x000000010dc4c358 //compute 方法
0x0000700001590898: 0x0000000000000000
0x00007000015908a0: 0x00007000015908c0 //main栈顶地址
0x00007000015908a8: 0x0000700001590918 //main桢地址
0x00007000015908b0: 0x000000010f461d80 //返回的地址
0x00007000015908b8: 0x0000000000000000 //变量s
0x00007000015908c0: 0x0000000000000014 //参数b
0x00007000015908c8: 0x000000000000000a //参数a
0x00007000015908d0: 0x0000000795779418 //参数MainTest <--变量指针
0x00007000015908d8: 0x00007000015908d8 //栈顶值 <--main 栈顶 sender sp
0x00007000015908e0: 0x000000010dc4c3f1 //代码地址
0x00007000015908e8: 0x0000700001590948 //变量指针
0x00007000015908f0: 0x000000010dc4c4b8 //常量池
0x00007000015908f8: 0x0000000000000000
0x0000700001590900: 0x000000010dc4c448 //main 方法
0x0000700001590908: 0x00007000015908c0
0x0000700001590910: 0x0000700001590948 //上一栈地址
0x0000700001590918: 0x00007000015909b0 //上一桢地址
0x0000700001590920: 0x000000010f45a7a7 //返回的地址
0x0000700001590928: 0x0000000000000000 //变量s
0x0000700001590930: 0x0000000000000014 //变量b
0x0000700001590938: 0x000000000000000a //变量a
0x0000700001590940: 0x0000000795779418 //参数MainTest
0x0000700001590948: 0x0000000795779408 //参数args <--变量指针
JVM规范中讲到的栈帧,操作数栈,本地变量表在Hotspot中其实都是汇编指令中用到的由CPU直接管理维护的栈帧,结合《JAVA运行栈》一文
- 局部变量表
当前Java方法对应的栈帧的底部的一段连续的内存空间而已.在开启一个新的Java栈帧式就会初始化完成并且在整个方法调用过程中所对应的的内存区域不变,其大小是固定的,根据方法编译后的本地变量大小确定
- 操作栈
操作数栈的变化跟栈帧的演变是一样的,栈顶不断向低地址方向演变。借用还没使用的栈区域使用。
- 动态连接
当前Java方法对应的栈帧中保存着常量池的引用,随时可以链接
- 返回地址
前Java方法对应的栈帧中保存着返回地址
主要参考
《hotspot实战》
《Java虚拟机规范8版》
《Hotspot 方法调用之JavaCalls 源码解析》
《Hotspot 方法调用之StubRoutines 源码解析》
《Hotspot 方法调用之StubGenerator 源码解析》
《Hotspot 字节码执行与栈顶缓存实现 源码解析》