Java Main 如何是如何被執(zhí)行的?
java應用程序的啟動在在/hotspot/src/share/tools/launcher/java.c的main()函數中,而在虛擬機 初始化過程中,將創(chuàng)建并啟動Java的Main線程。最后將調用JNIEnv的CallStaticVoidMethod()來執(zhí)行main方法。
CallStaticVoidMethod()對應的jni函數為jni_CallStaticVoidMethod,定義在/hotspot /src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod()又調用了 jni_invoke_static(),jni_invoke_static()通過JavaCalls的call()發(fā)起對Java方法的調用
所有來自虛擬機對Java函數的調用最終都將由JavaCalls模塊來完成,JavaCalls將通過call_helper()來執(zhí)行Java方法并返回調用結果,并最終調用StubRoutines::call_stub()來執(zhí)行Java方法:
- // do call
- { JavaCallWrapper link(method, receiver, result, CHECK);
- { HandleMark hm(thread); // HandleMark used by HandleMarkCleaner
- StubRoutines::call_stub()(
- (address)&link,
- // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
- result_val_address, // see NOTE above (compiler problem)
- result_type,
- method(),
- entry_point,
- args->parameters(),
- args->size_of_parameters(),
- CHECK
- );
- result = link.result(); // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
- // Preserve oop return value across possible gc points
- if (oop_result_flag) {
- thread->set_vm_result((oop) result->get_jobject());
- }
- }
- }
call_stub()定義在/hotspot/src/share/vm/runtime/stubRoutines.h中,實際上返回的就 是CallStub函數指針_call_stub_entry,該指針指向call_stub的匯編實現的目標代碼指令地址,即call_stub的例程 入口。
- // Calls to Java
- typedef void (*CallStub)(
- address link,
- intptr_t* result,
- BasicType result_type,
- methodOopDesc* method,
- address entry_point,
- intptr_t* parameters,
- int size_of_parameters,
- TRAPS
- );
- static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }
在分析call_stub的匯編代碼之前,先了解下x86寄存器和棧幀以及函數調用的相關知識。
x86-64的所有寄存器都是與 機器字長(數據總線位寬)相同,即64位的,x86-64將x86的8個32位通用寄存器擴展為64位(eax、ebx、ecx、edx、eci、 edi、ebp、esp),并且增加了8個新的64位寄存器(r8-r15),在命名方式上,也從”exx”變?yōu)?rdquo;rxx”,但仍保留”exx”進行32 位操作,下表描述了各寄存器的命名和作用
此外,還有16個128位的XMM寄存器,分別為xmm0-15,x84-64的寄存器遵循調用約定(Calling Conventions):
https://msdn.microsoft.com/en-US/library/zthk2dkh(v=vs.80).aspx
#p#
1.參數傳遞:
(1).前4個參數的int類型分別通過rcx、rdx、r8、r9傳遞,多余的在??臻g上傳遞(從右向左依次入棧),寄存器所有的參數都是向右對齊的(低位對齊)
(2).浮點數類型的參數通過xmm0-xmm3傳遞,注意不同類型的參數占用的寄存器序號是根據參數的序號來決定的,比如add(int,double,float,int)就分別保存在rcx、xmm1、xmm2、r9寄存器中
(3).8/16/32/64類型的結構體或共用體和_m64類型將使用rcx、rdx、r8、r9直接傳遞,而其他類型將會通過指針引用的方式在這4個寄存器中傳遞
(4).被調用函數當需要時要把寄存器中的參數移動到棧空間中(shadow space)
2.返回值傳遞
(1).對于可以填充為64位的返回值(包括_m64)將使用rax進行傳遞
(2).對于_m128(i/d)以及浮點數類型將使用xmm0傳遞
(3).對于64位以上的返回值,將由調用函數在棧上為其分配空間,并將其指針保存在rcx中作為”第一個參數”,而傳入參數將依次右移,最后函數調用完后,由rax返回該空間的指針
(4).用戶定義的返回值類型長度必須是1、2、4、8、16、32、64
3.調用者/被調用者保存寄存器
調用者保存寄存器:rax、rcx、rdx、r8-r11都認為是易失型寄存器(Volatile),這些寄存器隨時可能被用到,這些寄存器將由調用者 自行維護,當調用其他函數時,被調用函數對這些寄存器的操作并不會影響調用函數(即這些寄存器的作用范圍僅限于當前函數)。
被調用者保存寄 存器:rbx、rbp、rdi、rsi、r12-r15、xmm6-xmm15都是非易失型寄存器(non-volatile),調用其他函數時,這些寄 存器的值可能在調用返回時還需要用,那么被調用函數就必須將這些寄存器的值保存起來,當要返回時,恢復這些寄存器的值(即這些寄存器的作用范圍是跨函數調 用的)。
以如下程序為例,分析函數調用的棧幀布局:
- double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2)
- {
- int local_i1, local_i2;
- float local_f1;
- double local_d1;
- double local_d2 = 3.0;
- local_i1 = param_i1;
- local_i2 = param_i2;
- local_f1 = param_f1;
- local_d1 = param_d1;
- return local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2;
- }
- int main()
- {
- double res;
- res = func(1, 1.0, 2.0, 3, 3.0);
- return 0;
- }
main函數調用func之前的匯編代碼如下:
- main:
- pushq %rbp //保存rbp
- .seh_pushreg %rbp
- movq %rsp, %rbp //更新棧基址
- .seh_setframe %rbp, 0
- subq $80, %rsp
- .seh_stackalloc 80 //main棧需要80字節(jié)的??臻g
- .seh_endprologue
- call __main
- movabsq $4611686018427387904, %rdx //0x4000000000000000,即浮點數2.0
- movabsq $4613937818241073152, %rax //0x3000000000000000,即浮點數3.0
- movq %rax, 32(%rsp) //第5個參數3.0,即param_d2保存在??臻g上
- movl $3, %r9d //第4個參數3,即param_i2保存在r9d中(r9的低32位)
- movq %rdx, -24(%rbp)
- movsd -24(%rbp), %xmm2 //第3個參數2.0,即param_d1保存在xmm2中
- movss .LC2(%rip), %xmm1 //第2個參數1.0(0x3f800000),保存在xmm1中
- movl $1, %ecx //第1個參數1,保存在ecx中(rcx的低32位)
- call func
func函數返回后,main函數將從xmm0中取出返回結果
- call func
- movq %xmm0, %rax //保存結果
- movq %rax, -8(%rbp)
- movl $0, %eax //清空eax,回收main棧,恢復棧頂地址
- addq $80, %rsp
- popq %rbp
- ret
func函數的棧和操作數準備如下:
- func:
- pushq %rbp //保存rbp(main函數棧的基址)
- .seh_pushreg %rbp
- movq %rsp, %rbp //將main棧的棧頂指針作為被調用函數的棧基址
- .seh_setframe %rbp, 0
- subq $32, %rsp //func棧需要32字節(jié)的??臻g
- .seh_stackalloc 32
- .seh_endprologue
- movl %ecx, 16(%rbp) //將4個參數移動到棧底偏移16-40的空間(main棧的shadow space)
- movss %xmm1, 24(%rbp)
- movsd %xmm2, 32(%rbp)
- movl %r9d, 40(%rbp)
- movabsq $4613937818241073152, %rax //本地變量local_d2,即浮點數3.0
- movq %rax, -8(%rbp) //5個局部變量
- movl 16(%rbp), %eax
- movl %eax, -12(%rbp)
- movl 40(%rbp), %eax
- movl %eax, -16(%rbp)
- movl 24(%rbp), %eax
- movl %eax, -20(%rbp)
- movq 32(%rbp), %rax
- movq %rax, -32(%rbp)
隨后的func的運算過程如下:
- movl -16(%rbp), %eax //local_i2 - local_i1
- subl -12(%rbp), %eax
- pxor %xmm0, %xmm0 //準備xmm0寄存器,按位異或,xmm0清零
- cvtsi2ss %eax, %xmm0
- mulss -20(%rbp), %xmm0 //local_f1 * (local_i2 - local_i1)
- cvtss2sd %xmm0, %xmm0
- addsd -32(%rbp), %xmm0 //local_d1 + local_f1 * (local_i2 - local_i1)
- subsd 48(%rbp), %xmm0 //local_d1 + local_f1 * (local_i2 - local_i1) - param_d2
- addsd -8(%rbp), %xmm0 //local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2
- addq $32, %rsp //回收func棧,恢復棧頂地址
- popq %rbp
- ret
根據以上代碼分析,大概得出該程序調用棧結構:
這里沒有考慮func函數再次調用其他函數而準備操作數的棧內容的情況,但結合main函數棧,大致可以得出棧的通用結構如下:
call_stub由generate_call_stub()解釋成匯編代碼,有興趣的可以繼續(xù)閱讀call_stub的匯編代碼進行分析。
下面對call_stub的匯編部分進行分析:
先來看下call_stub的調用棧結構:(注:本文實驗是在windows_64位平臺上實現的)
- // Call stubs are used to call Java from C
- // return_from_Java 是緊跟在call *%eax后面的那條指令的地址
- // [ return_from_Java ] <--- rsp
- // -28 [ arguments ] <-- rbp - 0xe8
- // -26 [ saved xmm15 ] <-- rbp - 0xd8
- // -24 [ saved xmm14 ] <-- rbp - 0xc8
- // -22 [ saved xmm13 ] <-- rbp - 0xb8
- // -20 [ saved xmm12 ] <-- rbp - 0xa8
- // -18 [ saved xmm11 ] <-- rbp - 0x98
- // -16 [ saved xmm10 ] <-- rbp - 0x88
- // -14 [ saved xmm9 ] <-- rbp - 0x78
- // -12 [ saved xmm8 ] <-- rbp - 0x68
- // -10 [ saved xmm7 ] <-- rbp - 0x58
- // -9 [ saved xmm6 ] <-- rbp - 0x48
- // -7 [ saved r15 ] <-- rbp - 0x38
- // -6 [ saved r14 ] <-- rbp - 0x30
- // -5 [ saved r13 ] <-- rbp - 0x28
- // -4 [ saved r12 ] <-- rbp - 0x20
- // -3 [ saved rdi ] <-- rbp - 0x18
- // -2 [ saved rsi ] <-- rbp - 0x10
- // -1 [ saved rbx ] <-- rbp - 0x8
- // 0 [ saved rbp ] <--- rbp,
- // 1 [ return address ] <--- rbp + 0x08
- // 2 [ ptr. to call wrapper ] <--- rbp + 0x10
- // 3 [ result ] <--- rbp + 0x18
- // 4 [ result_type ] <--- rbp + 0x20
- // 5 [ method ] <--- rbp + 0x28
- // 6 [ entry_point ] <--- rbp + 0x30
- // 7 [ parameters ] <--- rbp + 0x38
- // 8 [ parameter_size ] <--- rbp + 0x40
- // 9 [ thread ] <--- rbp + 0x48
1.根據函數調用棧的結構:
在被調函數棧幀的棧底 %rbp + 8(棧地址向下增長,堆地址向上增長,棧底的正偏移值指向調用函數棧幀內容)保存著被調函數的傳入參數,這里即:
JavaCallWrapper指針、返回結果指針、返回結果類型、被調用方法的methodOop、被調用方法的解釋代碼的入口地址、參數地址、參數個數。
StubRoutines::call_stub [0x0000000002400567, 0x00000000024006cb[ (356 bytes)
//保存bp
0x0000000002400567: push %rbp
//更新棧頂地址
0x0000000002400568: mov %rsp,%rbp
//call_stub需要的??臻g大小為0xd8
0x000000000240056b: sub $0xd8,%rsp
2.rcx、rdx、r8d、r9d分別保存著傳入call_stub的前4個參數,現在需要將其復制到棧上的shadow space中
//分別使用rcx、rdx、r8、r9來保存第1、2、3、4個參數,多出來的其他參數用??臻g來傳遞
//使用xmm0-4來傳遞第1-4個浮點數參數
//這里將參數復制到棧空間,這樣call_stub的所有參數就在rbp + 0x10 ~ 0x48棧空間上
0x0000000002400572: mov %r9,0x28(%rbp)
0x0000000002400576: mov %r8d,0x20(%rbp)
0x000000000240057a: mov %rdx,0x18(%rbp)
0x000000000240057e: mov %rcx,0x10(%rbp)
3.將被調用者保存寄存器的值壓入call_stub棧中:
;; save registers:
//依次保存rbx、rsi、rdi這三個被調用者保存的寄存器,隨后保存r12-r15、XMM寄存器組xmm6-xmm15
0x0000000002400582: mov %rbx,-0x8(%rbp)
0x0000000002400586: mov %r12,-0x20(%rbp)
0x000000000240058a: mov %r13,-0x28(%rbp)
0x000000000240058e: mov %r14,-0x30(%rbp)
0x0000000002400592: mov %r15,-0x38(%rbp)
0x0000000002400596: vmovdqu %xmm6,-0x48(%rbp)
0x000000000240059b: vmovdqu %xmm7,-0x58(%rbp)
0x00000000024005a0: vmovdqu %xmm8,-0x68(%rbp)
0x00000000024005a5: vmovdqu %xmm9,-0x78(%rbp)
0x00000000024005aa: vmovdqu %xmm10,-0x88(%rbp)
0x00000000024005b2: vmovdqu %xmm11,-0x98(%rbp)
0x00000000024005ba: vmovdqu %xmm12,-0xa8(%rbp)
0x00000000024005c2: vmovdqu %xmm13,-0xb8(%rbp)
0x00000000024005ca: vmovdqu %xmm14,-0xc8(%rbp)
0x00000000024005d2: vmovdqu %xmm15,-0xd8(%rbp)
0x00000000024005da: mov %rsi,-0x10(%rbp)
0x00000000024005de: mov %rdi,-0x18(%rbp)
//棧底指針的0x48偏移保存著thread對象,0x6d01a2c3(%rip)為異常處理入口
0x00000000024005e2: mov 0x48(%rbp),%r15
0x00000000024005e6: mov 0x6d01a2c3(%rip),%r12 # 0x000000006f41a8b0
4.call_stub的參數保存著Java方法的參數,現在就需要將參數壓入call_stub棧中
/棧底指針的0x40偏移保存著參數的個數
0x00000000024005ed: mov 0x40(%rbp),%r9d
//若參數個數為0,則直接跳轉0x000000000240060d準備調用Java方法
0x00000000024005f1: test %r9d,%r9d
0x00000000024005f4: je 0x000000000240060d
//若參數個數不為0,則遍歷參數,將所有參數壓入本地棧
//其中棧底指針的0x38偏移保存著參數的地址,edx將用作循環(huán)的迭代器
0x00000000024005fa: mov 0x38(%rbp),%r8
0x00000000024005fe: mov %r9d,%edx
;; loop:
//從第一個參數開始,將Java方法的參數壓人本地棧
/*
* i = parameter_size; //確保不等于0
* do{
* push(parameter[i]);
* i--;
* }while(i!=0);
*/
0x0000000002400601: mov (%r8),%rax
0x0000000002400604: add $0x8,%r8
0x0000000002400608: dec %edx
0x000000000240060a: push %rax
0x000000000240060b: jne 0x0000000002400601
5.調用Java方法的解釋代碼
;; prepare entry:
//棧底指針的0x28和0x30偏移分別保存著被調用Java方法的methodOop指針和解釋代碼的入口地址
0x000000000240060d: mov 0x28(%rbp),%rbx
0x0000000002400611: mov 0x30(%rbp),%rdx
0x0000000002400615: mov %rsp,%r13 //保存棧頂指針
;; jump to run Java method:
0x0000000002400618: callq *%rdx
6.準備保存返回結果,這里需要先根據不同的返回類型取出返回結果,然后保存到返回結果指針所指向的位置
;; prepare to save result:
//棧底指針的0x18和0x20偏移分別保存著返回結果的指針和結果類型
0x000000000240061a: mov 0x18(%rbp),%rcx
0x000000000240061e: mov 0x20(%rbp),%edx
;; handle result accord to different result_type:
0x0000000002400621: cmp $0xc,%edx
0x0000000002400624: je 0x00000000024006b7
0x000000000240062a: cmp $0xb,%edx
0x000000000240062d: je 0x00000000024006b7
0x0000000002400633: cmp $0x6,%edx
0x0000000002400636: je 0x00000000024006bc
0x000000000240063c: cmp $0x7,%edx
0x000000000240063f: je 0x00000000024006c2
;; save result for the other result_type:
0x0000000002400645: mov %eax,(%rcx)
下面分別為返回結果類型為long、float、double的情況
;; long 類型返回結果保存:
0x00000000024006b7: mov %rax,(%rcx)
0x00000000024006ba: jmp 0x0000000002400647
;; float 類型返回結果保存:
0x00000000024006bc: vmovss %xmm0,(%rcx)
0x00000000024006c0: jmp 0x0000000002400647
;; double 類型返回結果保存:
0x00000000024006c2: vmovsd %xmm0,(%rcx)
0x00000000024006c6: jmpq 0x0000000002400647
7.被調用者保存寄存器的恢復,以及棧指針的復位
;; restore registers:
0x0000000002400647: lea -0xd8(%rbp),%rsp
0x000000000240064e: vmovdqu -0xd8(%rbp),%xmm15
0x0000000002400656: vmovdqu -0xc8(%rbp),%xmm14
0x000000000240065e: vmovdqu -0xb8(%rbp),%xmm13
0x0000000002400666: vmovdqu -0xa8(%rbp),%xmm12
0x000000000240066e: vmovdqu -0x98(%rbp),%xmm11
0x0000000002400676: vmovdqu -0x88(%rbp),%xmm10
0x000000000240067e: vmovdqu -0x78(%rbp),%xmm9
0x0000000002400683: vmovdqu -0x68(%rbp),%xmm8
0x0000000002400688: vmovdqu -0x58(%rbp),%xmm7
0x000000000240068d: vmovdqu -0x48(%rbp),%xmm6
0x0000000002400692: mov -0x38(%rbp),%r15
0x0000000002400696: mov -0x30(%rbp),%r14
0x000000000240069a: mov -0x28(%rbp),%r13
0x000000000240069e: mov -0x20(%rbp),%r12
0x00000000024006a2: mov -0x8(%rbp),%rbx
0x00000000024006a6: mov -0x18(%rbp),%rdi
0x00000000024006aa: mov -0x10(%rbp),%rsi
;; back to old(caller) stack frame:
0x00000000024006ae: add $0xd8,%rsp //棧頂指針復位
0x00000000024006b5: pop %rbp //棧底指針復位
0x00000000024006b6: retq
歸納出call_stub棧結構如下:
8.對于不同的Java方法,虛擬機在初始化時會生成不同的方法入口例程
(method entry point)來準備棧幀,這里以較常被使用的zerolocals方法入口為例,分析Java方法的棧幀結構與調用過程,入口例程目標代碼的產生在 InterpreterGenerator::generate_normal_entry()中:
(1).根據之前的分析,初始的棧結構如下:
獲取傳入參數數量到rcx中:
address InterpreterGenerator::generate_normal_entry(bool synchronized) {
// determine code generation flags
bool inc_counter = UseCompiler || CountCompiledCalls;
// ebx: methodOop
// r13: sender sp
address entry_point = __ pc();
const Address size_of_parameters(rbx,
methodOopDesc::size_of_parameters_offset());
const Address size_of_locals(rbx, methodOopDesc::size_of_locals_offset());
const Address invocation_counter(rbx,
methodOopDesc::invocation_counter_offset() +
InvocationCounter::counter_offset());
const Address access_flags(rbx, methodOopDesc::access_flags_offset());
// get parameter size (always needed)
__ load_unsigned_short(rcx, size_of_parameters);
其中methodOop指針被保存在rbx中,調用Java方法的sender sp被保存在r13中,參數大小保存在rcx中
(2). 獲取局部變量區(qū)的大小,保存在rdx中,并減去參數數量,將除參數以外的局部變量數量保存在rdx中(雖然參數作為局部變量是方法的一部分,但參數由調用 者提供,這些參數應有調用者棧幀而非被調用者棧幀維護,即被調用者棧幀只需要維護局部變量中除了參數的部分即可)
// rbx: methodOop
// rcx: size of parameters
// r13: 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
(3).對??臻g大小進行檢查,判斷是否會發(fā)生棧溢出
// see if we've got enough room on the stack for locals plus overhead.
generate_stack_overflow_check();
(4).獲取返回地址,保存在rax中(注意此時棧頂為調用函數call指令后下一條指令的地址)
// get return address
__ pop(rax);
(5).由于參數在棧中由低地址向高地址是以相反的順序存放的,所以第一個參數的地址應該是 rsp+rcx*8-8(第一個參數地址范圍為 rsp+rcx*8-8 ~ rsp+rcx*8),將其保存在r14中
// compute beginning of parameters (r14)
__ lea(r14, Address(rsp, rcx, Address::times_8, -wordSize))
(6).為除參數以外的局部變量分配棧空間,若這些局部變量數量為0,那么就跳過這一部分處理,否則,將壓入 maxlocals – param_size個0,以初始化這些局部變量
//該部分為一個loop循環(huán)
// 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);
}
這時棧的層次如下:
(7).將方法的調用次數保存在rcx/ecx中
// (pre-)fetch invocation count
if (inc_counter) {
__ movl(rcx, invocation_counter);
}
(8).初始化當前方法的棧幀
// initialize fixed part of activation frame
generate_fixed_frame(false);
generate_fixed_frame()的實現如下:
__ push(rax); // save return address
__ enter(); // save old & set new rbp
__ push(r13); // set sender sp
__ push((int)NULL_WORD); // leave last_sp as null
__ movptr(r13, Address(rbx, methodOopDesc::const_offset())); // get constMethodOop
__ lea(r13, Address(r13, constMethodOopDesc::codes_offset())); // get codebase
__ push(rbx);
保存返回地址,為被調用的Java方法準備棧幀,并將sender sp指針、last_sp(設置為0)壓入棧,根據methodOop的constMethodOop成員將字節(jié)碼指針保存到r13寄存器中,并將methodOop壓入棧
} else {
__ push(0); //methodData
}
__ movptr(rdx, Address(rbx, methodOopDesc::constants_offset()));
__ movptr(rdx, Address(rdx, constantPoolOopDesc::cache_offset_in_bytes()));
__ push(rdx); // set constant pool cache
__ push(r14); // set locals pointer
if (native_call) {
__ push(0); // no bcp
} else {
__ push(r13); // set bcp
}
__ push(0); // reserve word for pointer to expression stack bottom
__ movptr(Address(rsp, 0), rsp); // set expression stack bottom
}
將methodData以0為初始值壓入棧,根據methodOop的ConstantPoolOop成員將常量池緩沖地址壓入棧,r14中保存著 局部變量區(qū)(第一個參數的地址)指針,將其壓入棧,此外如果調用的是native調用,那么字節(jié)碼指針部分為0,否則正常將字節(jié)碼指針壓入棧,最后為棧留 出一個字的表達式棧底空間,并更新rsp
最后棧的空間結構如下:
(9).增加方法的調用計數
// 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);
}
}
(當調用深度過大會拋出StackOverFlow異常)
(10).同步方法的Monitor對象分配和方法的加鎖(在匯編部分分析中沒有該部分,如果對同步感興趣的請自行分析)
if (synchronized) {
// Allocate monitor and lock method
lock_method();
(11).JVM工具接口部分
// jvmti support
__ notify_method_entry();
(12).跳轉到第一條字節(jié)碼的本地代碼處執(zhí)行
__ dispatch_next(vtos);
以上分析可能略顯復雜,但重要的是明白方法的入口例程是如何為Java方法構造新的棧幀,從而為字節(jié)碼的運行提供調用棧環(huán)境。
method entry point匯編代碼的分析可以參考隨后的一篇文章。