天天看點

Java Main如何被執行?

從源碼到彙編目标代碼分析JVM(Hotspot)的方法調用過程與棧幀結構。

  java應用程式的啟動在在/hotspot/src/share/tools/launcher/java.c的main()函數中,而在虛拟機初始化過程中,将建立并啟動Java的Main線程。最後将調用JNIEnv的CallStaticVoidMethod()來執行main方法。

CallStaticVoidMethod()對應的jni函數為jni_CallStaticVoidMethod,定義在/hotspot/src/share/vm/prims/jni.cpp中,而jni_CallStaticVoidMethod()又調用了jni_invoke_static(),jni_invoke_static()通過JavaCalls的call()發起對Java方法的調用

  所有來自虛拟機對Java函數的調用最終都将由JavaCalls子產品來完成,JavaCalls将通過call_helper()來執行Java方法并傳回調用結果,并最終調用StubRoutines::call_stub()來執行Java方法:

1 // do call
 2   { JavaCallWrapper link(method, receiver, result, CHECK);
 3     { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner
 4 
 5       StubRoutines::call_stub()(
 6         (address)&link,
 7         // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
 8         result_val_address,          // see NOTE above (compiler problem)
 9         result_type,
10         method(),
11         entry_point,
12         args->parameters(),
13         args->size_of_parameters(),
14         CHECK
15       );
16 
17       result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
18       // Preserve oop return value across possible gc points
19       if (oop_result_flag) {
20         thread->set_vm_result((oop) result->get_jobject());
21       }
22     }
23   }      

  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”變為”rxx”,但仍保留”exx”進行32位操作,下表描述了各寄存器的命名和作用

Java Main如何被執行?

 此外,還有16個128位的XMM寄存器,分别為xmm0-15,x84-64的寄存器遵循調用約定(Calling Conventions): 

https://msdn.microsoft.com/en-US/library/zthk2dkh(v=vs.80).aspx 

1.參數傳遞: 

  (1).前4個參數的int類型分别通過rcx、rdx、r8、r9傳遞,多餘的在棧空間上傳遞(從右向左依次入棧),寄存器所有的參數都是向右對齊的(低位對齊) 

  (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),調用其他函數時,這些寄存器的值可能在調用傳回時還需要用,那麼被調用函數就必須将這些寄存器的值儲存起來,當要傳回時,恢複這些寄存器的值(即這些寄存器的作用範圍是跨函數調用的)。

  以如下程式為例,分析函數調用的棧幀布局:

1 double func(int param_i1, float param_f1, double param_d1, int param_i2, double param_d2)
 2 
 3 {
 4     int local_i1, local_i2;
 5     float local_f1;
 6     double local_d1;
 7     double local_d2 = 3.0;
 8     local_i1 = param_i1;
 9     local_i2 = param_i2;
10     local_f1 = param_f1;
11     local_d1 = param_d1;
12     return local_d1 + local_f1 * (local_i2 - local_i1) - param_d2 + local_d2;
13 }
14 
15 int main()
16 
17 {
18     double res;
19     res = func(1, 1.0, 2.0, 3, 3.0);
20     return 0;
21 }      

main函數調用func之前的彙編代碼如下:

main:
    pushq   %rbp            //儲存rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp      //更新棧基址
    .seh_setframe   %rbp, 0
    subq    $80, %rsp      
    .seh_stackalloc 80      //main棧需要80位元組的棧空間
    .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儲存在棧空間上
    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位元組的棧空間
    .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      

根據以上代碼分析,大概得出該程式調用棧結構:

Java Main如何被執行?

這裡沒有考慮func函數再次調用其他函數而準備操作數的棧内容的情況,但結合main函數棧,大緻可以得出棧的通用結構如下: 

Java Main如何被執行?

call_stub由generate_call_stub()解釋成彙編代碼,有興趣的可以繼續閱讀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需要的棧空間大小為0xd8
  0x000000000240056b: sub    $0xd8,%rsp      

2.rcx、rdx、r8d、r9d分别儲存着傳入call_stub的前4個參數,現在需要将其複制到棧上的shadow space中

//分别使用rcx、rdx、r8、r9來儲存第1、2、3、4個參數,多出來的其他參數用棧空間來傳遞
  //使用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将用作循環的疊代器
  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棧結構如下: 

Java Main如何被執行?

8.對于不同的Java方法,虛拟機在初始化時會生成不同的方法入口例程 

(method entry point)來準備棧幀,這裡以較常被使用的zerolocals方法入口為例,分析Java方法的棧幀結構與調用過程,入口例程目标代碼的産生在InterpreterGenerator::generate_normal_entry()中: 

(1).根據之前的分析,初始的棧結構如下: 

Java Main如何被執行?

擷取傳入參數數量到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).擷取局部變量區的大小,儲存在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).對棧空間大小進行檢查,判斷是否會發生棧溢出

// 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循環
// 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);
  }      

這時棧的層次如下:

Java Main如何被執行?

(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成員将位元組碼指針儲存到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中儲存着局部變量區(第一個參數的位址)指針,将其壓入棧,此外如果調用的是native調用,那麼位元組碼指針部分為0,否則正常将位元組碼指針壓入棧,最後為棧留出一個字的表達式棧底空間,并更新rsp

最後棧的空間結構如下: 

Java Main如何被執行?

(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).跳轉到第一條位元組碼的本地代碼處執行

__ dispatch_next(vtos);      

以上分析可能略顯複雜,但重要的是明白方法的入口例程是如何為Java方法構造新的棧幀,進而為位元組碼的運作提供調用棧環境。

method entry point彙編代碼的分析可以參考随後的一篇文章。

繼續閱讀