心頭一直有個疑問,jvm虛擬是如何對接class中的字節碼的?或者說在未進入
JIT優化階段時,解釋器是如何對接的?html
hotspot經過C++代碼在堆上申請一塊空間,向裏面填充一組指令,而後把這塊空間當成一個函數,經過函數指針去調用剛生成的代碼。是否是666,是否是哇超酷斃了。java
address generate_call_stub(address& return_address) { // TODO: 須要一路仔細調試 c++方法 返回無符號char 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(); // SimonNote: 這個方法最終返回的就是這個start 宏展開 : _masm->pc() address start = _masm-> pc(); // SimonNote: _code_section 的end 是pc() 也是此處的start // same as in generate_catch_exception()! const Address rsp_after_call(rbp, rsp_after_call_off * wordSize); // 這些adress都是準備工做 真正在內存區域生成彙編指令的事情是在下面的作的 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); 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 __ enter(); //SimonNote: macroAssembler_x86.cpp MacroAssembler::enter() push(rbp); mov(rbp, rsp); 真正在內存區域生成彙編指令!其實就是把指令等opcode寫入內存區域 __ subptr(rsp, -rsp_after_call_off * wordSize); // save register parameters #ifndef _WIN64 __ movptr(parameters, c_rarg5); // parameters __ movptr(entry_point, c_rarg4); // entry_point #endif __ movptr(method, c_rarg3); // method __ movl(result_type, c_rarg2); // result type __ movptr(result, c_rarg1); // result __ movptr(call_wrapper, c_rarg0); // call wrapper // save regs belonging to calling function __ 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 __ movptr(r15_thread, thread); __ reinit_heapbase(); #ifdef ASSERT // make sure we have no pending exceptions { Label L; __ cmpptr(Address(r15_thread, Thread::pending_exception_offset()), (int32_t)NULL_WORD); __ jcc(Assembler::equal, L); __ stop("StubRoutines::call_stub: entered with pending exception"); __ bind(L); } #endif // pass parameters if any BLOCK_COMMENT("pass parameters if any"); Label parameters_done; __ movl(c_rarg3, parameter_size); __ testl(c_rarg3, c_rarg3); __ jcc(Assembler::zero, parameters_done); Label loop; __ movptr(c_rarg2, parameters); // parameter pointer __ movl(c_rarg1, c_rarg3); // parameter counter is in c_rarg1 __ BIND(loop); __ movptr(rax, Address(c_rarg2, 0));// get parameter __ addptr(c_rarg2, wordSize); // advance to next parameter __ decrementl(c_rarg1); // decrement counter __ push(rax); // pass parameter __ jcc(Assembler::notZero, loop); // call Java function __ BIND(parameters_done); __ movptr(rbx, method); // get Method* __ movptr(c_rarg1, entry_point); // get entry_point __ mov(r13, rsp); // set sender sp BLOCK_COMMENT("call Java function"); __ call(c_rarg1); BLOCK_COMMENT("call_stub_return_address:"); return_address = __ pc(); // store result depending on type (everything that is not // T_OBJECT, T_LONG, T_FLOAT or T_DOUBLE is treated as T_INT) __ movptr(c_rarg0, result); Label is_long, is_float, is_double, exit; __ 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); // handle T_INT case __ movl(Address(c_rarg0, 0), rax); __ BIND(exit); // pop parameters __ lea(rsp, rsp_after_call); #ifdef ASSERT // verify that threads correspond { Label L, S; __ cmpptr(r15_thread, thread); __ jcc(Assembler::notEqual, S); __ get_thread(rbx); __ cmpptr(r15_thread, rbx); __ jcc(Assembler::equal, L); __ bind(S); __ jcc(Assembler::equal, L); __ stop("StubRoutines::call_stub: threads must correspond"); __ bind(L); } #endif // 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 // restore rsp __ addptr(rsp, -rsp_after_call_off * wordSize); // return __ pop(rbp); __ ret(0); // handle return types different from T_INT __ BIND(is_long); __ 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; }
上述函數是在JVM啓動初始化會調用這個。函數返回的是adress,其類型定義以下:c++
typedef unsigned char u_char; typedef u_char* address;
C++中是能夠將對象this轉換成unsigned char指針的。adress的值就是對象this的地址。示例程序參見《C++中將對象this轉換成unsigned char指針》sass
address start = _masm-> pc(); // SimonNote: _code_section 的end 是pc() 也是此處的start
_masmapp
__是一個宏,展開後是 _masm->
_masm變量的初始化在stubCodeGenerator.cpp中的StubCodeGenerator::StubCodeGenerator函數裏jvm
StubCodeGenerator::StubCodeGenerator(CodeBuffer* code, bool print_code) { _masm = new MacroAssembler(code); _first_stub = _last_stub = NULL; _print_code = print_code; }
貼一下到初始化_masm的調用棧ide
StubCodeGenerator::StubCodeGenerator() at stubCodeGenerator.cpp:72 0x7ffff69d60d8 ICacheStubGenerator::ICacheStubGenerator() at icache.hpp:91 0x7ffff65da534 AbstractICache::initialize() at icache.cpp:39 0x7ffff65da324 icache_init() at icache.cpp:105 0x7ffff65da4f0 CodeCache::initialize() at codeCache.cpp:572 0x7ffff63d09f8 codeCache_init() at codeCache.cpp:582 0x7ffff63d0a34 init_globals() at init.cpp:98 0x7ffff65e93c5 Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8 JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f JavaMain() at java.c:371 0x7ffff7bbf9e8 start_thread() at pthread_create.c:463 0x7ffff73ab6db clone() at clone.S:95 0x7ffff78e888f
往上翻幾個棧不難看出:
_masm(MacroAssembler須要的code是CodeBuffer,
CodeBuffer又須要BufferBlob
這段邏輯在icache.cpp的 void AbstractICache::initialize()中函數
void AbstractICache::initialize() { // Making this stub must be FIRST use of assembler ResourceMark rm; BufferBlob* b = BufferBlob::create("flush_icache_stub", ICache::stub_size); CodeBuffer c(b); ICacheStubGenerator g(&c); g.generate_icache_flush(&_flush_icache_stub); // The first use of flush_icache_stub must apply it to itself. // The StubCodeMark destructor in generate_icache_flush will // call Assembler::flush, which in turn will call invalidate_range, // which will in turn call the flush stub. Thus we don't need an // explicit call to invalidate_range here. This assumption is // checked in invalidate_range. }
BufferBlob* b = BufferBlob::create("flush_icache_stub", ICache::stub_size);作了空間分配,下面講;oop
分配的棧優化
HeapBlock::allocated_space() at heap.hpp:54 0x7ffff65c26e4 CodeHeap::allocate() at heap.cpp:219 0x7ffff65c1a25 CodeCache::allocate() at codeCache.cpp:186 0x7ffff63cf5d8 BufferBlob::operator new() at codeBlob.cpp:249 0x7ffff63c8a0b BufferBlob::create() at codeBlob.cpp:218 0x7ffff63c884e AbstractICache::initialize() at icache.cpp:36 0x7ffff65da2eb icache_init() at icache.cpp:105 0x7ffff65da4f0 CodeCache::initialize() at codeCache.cpp:572 0x7ffff63d09f8 codeCache_init() at codeCache.cpp:582 0x7ffff63d0a34 init_globals() at init.cpp:98 0x7ffff65e93c5 Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8 JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f JavaMain() at java.c:371 0x7ffff7bbf9e8 start_thread() at pthread_create.c:463 0x7ffff73ab6db clone() at clone.S:95 0x7ffff78e888f
有一段代碼一開始沒讀懂,在同事幫助下才看明白:
BufferBlob* BufferBlob::create(const char* name, int buffer_size) { ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock BufferBlob* blob = NULL; unsigned int size = sizeof(BufferBlob); // align the size to CodeEntryAlignment size = align_code_offset(size); size += round_to(buffer_size, oopSize); assert(name != NULL, "must provide a name"); { MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); blob = new (size) BufferBlob(name, size); // 這行是什麼意思?怎麼有這種寫法? } // Track memory usage statistic after releasing CodeCache_lock MemoryService::track_code_cache_memory_usage(); return blob; }
BufferBlob* blob = new (size) BufferBlob(name, size); 這種寫法是placement new的寫法,在《The C++ Programming Language, 4th Edition》中11.2.4 Overloading new有講解。按我粗淺的不嚴謹的寫法理解成:
在建立對象時,給對象指定分配在哪一個內存地址上,place object of size sz at p。
不過這裏的寫法,跟最簡單的placement new寫法仍是有點差別,它作了重載,最原始的寫法是:
void∗ buf = reinterpret_cast<void∗>(0xF00F); // significant address X∗ p2 = new(buf) X; // construct an X at buf; // invokes: operator new(sizeof(X),buf) // The ‘‘placement’’ operator new() is the simplest such allocator. It is defined in the standard header <new>: void∗ operator new (size_t sz, void∗ p) noexcept; // place object of size sz at p
對於operator new的第一個參數size_t sz在調用時不用傳送,由編譯器在編譯時決定並送進來,使用者只要送void* p就能夠了
上面BufferBlob的代碼對operator new還作了重載,(這也是一般的作法,自定義內存分配邏輯,並返回分配的指針):
void* BufferBlob::operator new(size_t s, unsigned size, bool is_critical) throw() { void* p = CodeCache::allocate(size, is_critical); return p; }
將字節碼翻譯成彙編指令的調用棧:
以new爲例:
TemplateTable::_new() at templateTable_x86_64.cpp:3,250 0x7ffff6a39ed6 Template::generate() at templateTable.cpp:63 0x7ffff6a267c7 TemplateInterpreterGenerator::generate_and_dispatch() at templateInterpreter.cpp:530 0x7ffff6a1c392 TemplateInterpreterGenerator::set_vtos_entry_points() at templateInterpreter_x86_64.cpp:2,039 0x7ffff6a25dfc TemplateInterpreterGenerator::set_short_entry_points() at templateInterpreter.cpp:498 0x7ffff6a1c179 TemplateInterpreterGenerator::set_entry_points() at templateInterpreter.cpp:464 0x7ffff6a1bcb3 TemplateInterpreterGenerator::set_entry_points_for_all_bytes() at templateInterpreter.cpp:421 0x7ffff6a1b974 TemplateInterpreterGenerator::generate_all() at templateInterpreter.cpp:402 0x7ffff6a1b8d6 InterpreterGenerator::InterpreterGenerator() at templateInterpreter_x86_64.cpp:2,051 0x7ffff6a25e3b TemplateInterpreter::initialize() at templateInterpreter.cpp:52 0x7ffff6a19aab interpreter_init() at interpreter.cpp:118 0x7ffff664c2fe init_globals() at init.cpp:107 0x7ffff65e93ef Threads::create_vm() at thread.cpp:3,424 0x7ffff6a471c8 JNI_CreateJavaVM() at jni.cpp:5,166 0x7ffff66a156d InitializeJVM() at java.c:1,145 0x7ffff7bc1b1f JavaMain() at java.c:371 0x7ffff7bbf9e8 start_thread() at pthread_create.c:463 0x7ffff73ab6db clone() at clone.S:95 0x7ffff78e888f
這個棧很重要! 子節碼都會按這個套路生成彙編指令模板。
在解釋彙編指令時再用跳轉指令跳轉到指令模板入口處的地址。
能夠經過在調試TemplateTable::_new()
代碼時,按以下路線查下_masm
對應的_code_section
對應的_start
內存地址,而後經過CDT的Disassembly視圖定位到相應的內存地址,並查看生成的指令,一目瞭然。此辦法一樣適用於generate_call_stub
生成模板指令的地方的調試:
MacroAssembler MacroAssembler {...} Assembler Assembler {...} AbstractAssembler AbstractAssembler {...} ResourceObj ResourceObj {...} _code_section CodeSection * 0x7ffff7fdd5e0 _start address 0x7fffe10449e0 "Pé*"
[inside hotspot] 彙編模板解釋器(Template Interpreter)和字節碼執行
[討論] HotSpot 解釋器是怎樣執行bytecode 的
[討論] 請教:Java 字節碼如何執行的
[討論] java_main的彙編入口在哪裏
運行時對代碼操縱的一個小demo
JVM 模板解釋器之如何根據字節碼生成彙編碼?