通過《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 字節碼執行與棧頂緩存實現 源碼解析》