第4篇-JVM終於開始調用Java主類的main()方法啦


在前一篇 第3篇-CallStub新棧幀的創建 中我們介紹了generate_call_stub()函數的部分實現,完成了向CallStub棧幀中壓入參數的操作,此時的狀態如下圖所示。

繼續看generate_call_stub()函數的實現,接來下會加載線程寄存器,代碼如下:

__ movptr(r15_thread, thread);
__ reinit_heapbase();

生成的匯編代碼如下:

mov    0x18(%rbp),%r15  
mov    0x1764212b(%rip),%r12   # 0x00007fdf5c6428a8

對照着上面的棧幀可看一下0x18(%rbp)這個位置存儲的是thread,將這個參數存儲到%r15寄存器中。

如果在調用函數時有參數的話需要傳遞參數,代碼如下:

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);

// 如果執行下面的邏輯,那么就表示parameter_size的值不為0,也就是需要為
// 調用的java方法提供參數
Label loop;
// 將地址parameters包含的數據即參數對象的指針拷貝到c_rarg2寄存器中
__ movptr(c_rarg2, parameters);       
// 將c_rarg3中值拷貝到c_rarg1中,即將參數個數復制到c_rarg1中
__ movl(c_rarg1, c_rarg3);            
__ BIND(loop);
// 將c_rarg2指向的內存中包含的地址復制到rax中
__ movptr(rax, Address(c_rarg2, 0));
// c_rarg2中的參數對象的指針加上指針寬度8字節,即指向下一個參數
__ addptr(c_rarg2, wordSize);       
// 將c_rarg1中的值減一
__ decrementl(c_rarg1);            
// 傳遞方法調用參數
__ push(rax);                       
// 如果參數個數大於0則跳轉到loop繼續
__ jcc(Assembler::notZero, loop);

這里是個循環,用於傳遞參數,相當於如下代碼:

while(%esi){
   rax = *arg
   push_arg(rax)
   arg++;   // ptr++
   %esi--;  // counter--
}

生成的匯編代碼如下:

// 將棧中parameter size送到%ecx中
mov    0x10(%rbp),%ecx   
// 做與運算,只有當%ecx中的值為0時才等於0 
test   %ecx,%ecx          
// 沒有參數需要傳遞,直接跳轉到parameters_done即可
je     0x00007fdf4500079a 
// -- loop --
// 匯編執行到這里,說明paramter size不為0,需要傳遞參數
mov    -0x8(%rbp),%rdx
mov    %ecx,%esi
mov    (%rdx),%rax
add    $0x8,%rdx
dec    %esi
push   %rax
// 跳轉到loop
jne    0x00007fdf4500078e  

因為要調用Java方法,所以會為Java方法壓入實際的參數,也就是壓入parameter size個從parameters開始取的參數。壓入參數后的棧如下圖所示。

當把需要調用Java方法的參數准備就緒后,接下來就會調用Java方法。這里需要重點提示一下Java解釋執行時的方法調用約定,不像C/C++在x86下的調用約定一樣,不需要通過寄存器來傳遞參數,而是通過棧來傳遞參數的,說的更直白一些,是通過局部變量表來傳遞參數的,所以上圖CallStub()函數棧幀中的argument word1 ... argument word n其實是​被調用的Java方法局部變量表的一部分。

下面接着看調用Java方法的代碼,如下:

// 調用Java方法
// -- parameters_done --

__ BIND(parameters_done); 
// 將method地址包含的數據接Method*拷貝到rbx中
__ movptr(rbx, method);            
// 將解釋器的入口地址拷貝到c_rarg1寄存器中
__ movptr(c_rarg1, entry_point);    
// 將rsp寄存器的數據拷貝到r13寄存器中
__ mov(r13, rsp);                   

// 調用解釋器的解釋函數,從而調用Java方法
// 調用的時候傳遞c_rarg1,也就是解釋器的入口地址
__ call(c_rarg1); 

生成的匯編代碼如下:

// 將Method*送到%rbx中
mov     -0x18(%rbp),%rbx  
// 將entry_point送到%rsi中
mov     -0x10(%rbp),%rsi  
// 將調用者的棧頂指針保存到%r13中
mov     %rsp,%r13    
// 調用Java方法     
callq   *%rsi             

注意調用callq指令后,會將callq指令的下一條指令的地址壓棧,再跳轉到第1操作數指定的地址,也就是*%rsi表示的地址。壓入下一條指令的地址是為了讓函數能通過跳轉到棧上的地址從子函數返回。 

callq指令調用的是entry_point。entry_point在后面會詳細介紹。

公眾號 深入剖析Java虛擬機HotSpot 已經更新虛擬機源代碼剖析相關文章到60+,歡迎關注,如果有任何問題,可加作者微信mazhimazh,拉你入虛擬機群交流


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



猜您在找 第29篇-調用Java主類的main()方法 java 主類的main方法調用其他方法 Java中是否可以調用一個類中的main方法? java main方法也可以被其他方法調用 4.編寫Java應用程序。首先,定義一個時鍾類——Clock,它包括三個int型 成員變量分別表示時、分、秒,一個構造方法用於對三個成員變量(時、分、秒) 進行初始化,還有一個成員方法show()用於顯示時鍾對象的時間。其次,再定義 一個主類——TestClass,在主類的main方法中創建多個時鍾類的對象,使用這 些對象調用方法show()來顯示時鍾的時間。 編寫一個類A,該類創建的對象可以調用方法f輸出小寫的英文字母表。然 后再編寫一個A類的子類B,要求子類B必須繼承類A的方法f(不允許重寫), 子類B創建的對象不僅可以調用方法f輸出小寫的英文字母表,而且可以調用子 類新增的方法g輸出大寫的英文字母表。最后編寫主類C,在主類的main方法 中測試類A與類B。 編寫Java應用程序。首先,定義描述學生的類——Student,包括學號(int)、 姓名(String)、年齡(int)等屬性;二個方法:Student(int stuNo,String name,int age) 用於對對象的初始化,outPut()用於輸出學生信息。其次,再定義一個主類—— TestClass,在主類的main方法中創建多個Student類的對象,使用這些對象來測 試Stud JAVA 主函數(主方法) 首先,定義一個時鍾類——Clock,它包括三個int型 成員變量分別表示時、分、秒,一個構造方法用於對三個成員變量(時、分、秒) 進行初始化,還有一個成員方法show()用於顯示時鍾對象的時間。其次,再定義 一個主類——TestClass,在主類的main方法中創建多個時鍾類的對象,使用這 些對象調用方法show()來顯示時鍾的時間。 IDEA創建JAVA項目常見問題:找不到或無法加載主類 Main的解決辦法
 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM