第44篇-為native方法設置解釋執行入口

  • 2021 年 12 月 15 日
  • 筆記

對於Java中的native方法來說,實際上調用的是C/C++實現的本地函數,由於可能會在Java解釋執行過程中調用native方法,或在本地函數的實現過程中調用Java方法,所以當兩者相互調用時,必須要遵守調用約定,同時要保證在被調用方法執行完成後,調用者的方法能繼續向下執行。

在HotSpot VM中,Java方法調用native方法會有2個入口常式,一個為解釋執行的入口常式,一個為「編譯」執行的入口常式。所謂「編譯」執行其實是如果一個native方法在解釋模式被調用到了CompileThreshold次數之後,HotSpot VM會為該方法專門生成一個Native wrapper,將其方法屬性、參數遷移之類的資訊都固化進去,相比解釋執行開銷會小一些。Native wrapper生成好之後會保存到方法的Method::_from_compiled_entry屬性中。

這一篇我們先介紹為解釋執行生成的常式。

1、InterpreterGenerator::generate_native_entry()函數生成解釋執行的入口常式

之前介紹過調用InterpreterGenerator::generate_normal_entry()函數生成Java方法解釋執行的入口,而調用InterpreterGenerator::generate_native_entry()函數會生成native方法的入口,最終會將生成的常式入口保存到Interpreter::_entry_table一維數組中,通過MethodKind來從一維數組中獲取對應方法類型的常式。

InterpreterGenerator::generate_native_entry()函數生成的常式的邏輯比較多,我們分幾部分來解讀。

(1)生成native方法的棧幀

下面詳細介紹生成native方法棧幀的生成過程。

// 在調用此常式時,各個暫存器中的值如下:
// rbx: Method*
// r13: sender sp
 
0x00007fffe1014c00: mov    0x10(%rbx),%rcx // 將ConstMethod*存儲到%rcx中
0x00007fffe1014c04: movzwl 0x2a(%rcx),%ecx // 將參數的大小存儲到%ecx中
0x00007fffe1014c08: pop    %rax            // 將返回地址彈出到%rax中
 
// rbx: Method*
// rcx: size of parameters 通過上面的操作,將參數的大小存儲到rcx暫存器中,如果是int、byte等為1個slot,而long和double為2個slot,所以這裡指的是需要slot的大小
// r13: sender sp
// for natives the size of locals is zero
// compute beginning of parameters (r14)
 
// 根據%rsp和參數大小計算參數的地址
// %r14指向棧頂第一個參數的位置
0x00007fffe1014c09: lea    -0x8(%rsp,%rcx,8),%r14 

// 為本地調用初始化兩個8位元組的數據,其中一個保存result_handler,一個保存oop temp
0x00007fffe1014c0e: pushq  $0x0
// oop temp對於靜態的native方法來說,保存的可能是mirror,
// 或者native方法調用結果為對象時,保存這個對象
0x00007fffe1014c13: pushq  $0x0

需要注意,在InterpreterGenerator::generate_native_entry()中調用generate_fixed_frame()方法之前,會開闢2個8位元組的空間,分別用來存放result_handler和oop temp,而在 InterpreterGenerator::generate_normal_entry()函數中調用時不會開闢這2個8位元組的空間。

如上彙編執行完成後的棧圖如下所示。

調用generate_fixed_frame()函數生成的彙編如下: 

0x00007fffe1014c18: push   %rax
 
0x00007fffe1014c19: push   %rbp
0x00007fffe1014c1a: mov    %rsp,%rbp
 
0x00007fffe1014c1d: push   %r13
0x00007fffe1014c1f: pushq  $0x0
0x00007fffe1014c24: mov    0x10(%rbx),%r13
0x00007fffe1014c28: lea    0x30(%r13),%r13
0x00007fffe1014c2c: push   %rbx
0x00007fffe1014c2d: mov    0x18(%rbx),%rdx
0x00007fffe1014c31: test   %rdx,%rdx
0x00007fffe1014c34: je     0x00007fffe1014c41
0x00007fffe1014c3a: add    $0x90,%rdx
0x00007fffe1014c41: push   %rdx
0x00007fffe1014c42: mov    0x10(%rbx),%rdx
0x00007fffe1014c46: mov    0x8(%rdx),%rdx
0x00007fffe1014c4a: mov    0x18(%rdx),%rdx
0x00007fffe1014c4e: push   %rdx
0x00007fffe1014c4f: push   %r14
0x00007fffe1014c51: pushq  $0x0
0x00007fffe1014c56: pushq  $0x0
0x00007fffe1014c5b: mov    %rsp,(%rsp)  

如上彙編程式碼在之前介紹「為Java方法創建新棧幀」時詳細介紹過,邏輯基本類似,這裡不再過多介紹。

執行完如上彙編後的棧幀狀態如下:   

 

接下來生成的彙編片段如下(可以添加虛擬機參數-XX:-UseStackBanging和-XX:-ProfileInterpreter,這樣暫時不生成棧檢查和統計相關的彙編程式碼,我們只關注最主要的邏輯): 

// 將JavaThread::do_unlock_if_synchronized變數設置為true
0x00007fffe1014c5f: movb   $0x1,0x2ad(%r15)     
// ... 計數等操作
// 將JavaThread::do_unlock_if_synchronized變數設置為false
0x00007fffe1014d48: movb   $0x0,0x2ad(%r15)     
 
// 省略調用lock_method()函數生成的彙編程式碼,如果是同步方法,
// 則還會調用lock_method()函數生成相關的彙編程式碼
 
// 省略調用__ notify_method_entry()函數生成的彙編程式碼,
// 不會對當前棧幀的布局產生任何影響
 
// 從棧幀中取出Method*存儲到%rbx中
0x00007fffe1014d87: mov    -0x18(%rbp),%rbx    
// 獲取ConstMethod*存儲到%r11中
0x00007fffe1014d8b: mov    0x10(%rbx),%r11    
 // 將方法參數的大小放到%r11d中 
0x00007fffe1014d8f: movzwl 0x2a(%r11),%r11d   
// 將%r11d中的內容左移3位,也就是算出方法參數需要佔用的位元組數
0x00007fffe1014d94: shl    $0x3,%r11d      
// 更新%rsp的值,為方法參數開闢存儲參數的空間   
0x00007fffe1014d98: sub    %r11,%rsp   
// 對linux系統來說不起作用       
0x00007fffe1014d9b: sub    $0x0,%rsp           
// 必須是16位元組邊界(see amd64 ABI)
0x00007fffe1014d9f: and    $0xfffffffffffffff0,%rsp  

System V / AMD64 ABI要求16位元組堆棧對齊。詳細資訊可以參考其它文章。

(2)連接Method::signature_handler並調用

下面繼續看彙編的邏輯:

// 將Method::signature_handler存儲到%r11中
0x00007fffe1014da3: mov 0x68(%rbx),%r11
// 如果Method::signature_handler非空,則跳轉到L1
0x00007fffe1014da7: test   %r11,%r11
0x00007fffe1014daa: jne    0x00007fffe1014e40  
 
 
// 執行到這裡,說明Method::signature_handler屬性的值為空,
// 需要調用InterpreterRuntime::prepare_native_call()函數
// 確保native方法已經綁定且安裝了方法簽名解析程式碼
// 調用__ call_VM()函數生成如下常式
0x00007fffe1014db0: callq  0x00007fffe1014dba
0x00007fffe1014db5: jmpq   0x00007fffe1014e38
0x00007fffe1014dba: mov    %rbx,%rsi
0x00007fffe1014dbd: lea    0x8(%rsp),%rax
0x00007fffe1014dc2: mov    %r13,-0x38(%rbp)
0x00007fffe1014dc6: mov    %r15,%rdi
0x00007fffe1014dc9: mov    %rbp,0x200(%r15)
0x00007fffe1014dd0: mov    %rax,0x1f0(%r15)
0x00007fffe1014dd7: test   $0xf,%esp
0x00007fffe1014ddd: je     0x00007fffe1014df5
0x00007fffe1014de3: sub    $0x8,%rsp
0x00007fffe1014de7: callq  0x00007ffff66af6aa
0x00007fffe1014dec: add    $0x8,%rsp
0x00007fffe1014df0: jmpq   0x00007fffe1014dfa
0x00007fffe1014df5: callq  0x00007ffff66af6aa
0x00007fffe1014dfa: movabs $0x0,%r10
0x00007fffe1014e04: mov    %r10,0x1f0(%r15)
0x00007fffe1014e0b: movabs $0x0,%r10
0x00007fffe1014e15: mov    %r10,0x200(%r15)
0x00007fffe1014e1c: cmpq   $0x0,0x8(%r15)
0x00007fffe1014e24: je     0x00007fffe1014e2f
0x00007fffe1014e2a: jmpq   0x00007fffe1000420
0x00007fffe1014e2f: mov    -0x38(%rbp),%r13
0x00007fffe1014e33: mov    -0x30(%rbp),%r14
0x00007fffe1014e37: retq   
// 結束__ call_VM()函數的調用

 
// 將Method*存儲到%rbx中 
0x00007fffe1014e38: mov    -0x18(%rbp),%rbx  
// 將Method::signature_handler放到%11中
0x00007fffe1014e3c: mov    0x68(%rbx),%r11  
 
// **** L1 ****
// 從這裡開始執行如下的程式碼邏輯時,Method::signature_handler已經放到了%r11中


// 調用Method::signature_handler函數
0x00007fffe1014e40: callq  *%r11           
// 調用signature_handler,解析方法參數,整個
// 過程一般不會改變rbx,但是慢速處理時可能導致GC,
// 所以調用完成後最好重新獲取Method
0x00007fffe1014e43: mov    -0x18(%rbp),%rbx  
// 將%rax中的result_handler存儲到方法棧幀中,result_handler
// 是執行signature_handler常式後的返回值,根據方法簽名的返回類型獲取的
0x00007fffe1014e47: mov    %rax,0x18(%rbp)

調用的InterpreterRuntime::prepare_native_call()函數會查找native方法對應的本地函數實現並存儲到Method的native_function中,同時還會創建signature_handler並存儲到Method實例中。我們在《深入剖析Java虛擬機:源碼剖析與實例詳解(基礎卷)》中介紹過Method類,如果是表示native方法的Method實例,那麼在為Method實例分配記憶體時會多分配2個指針大小的空間,如下圖所示。

關於native function和signature handler我們後面介紹。 

(3)為靜態的native方法準備mirror參數

回到InterpreterGenerator::generate_native_entry()函數,繼續查看生成的彙編程式碼,如下:  

// 將Method::access_flags存儲到%r11d中
0x00007fffe1014e4b: mov    0x28(%rbx),%r11d   
// 判斷是否為static本地方法,其中$0x8表示JVM_ACC_STATIC
0x00007fffe1014e4f: test   $0x8,%r11d        
// 如果為0,表示是非static方法,要跳轉到-- L2 --
0x00007fffe1014e56: je     0x00007fffe1014e74 
 

// 執行這裡程式碼時,說明方法是static方法
// 如下4個mov指令將通過Method->ConstMehod->ConstantPool->mirror
// 獲取到java.lang.Class的oop
0x00007fffe1014e5c: mov    0x10(%rbx),%r11
0x00007fffe1014e60: mov    0x8(%r11),%r11
0x00007fffe1014e64: mov    0x20(%r11),%r11
0x00007fffe1014e68: mov    0x70(%r11),%r11
// 將mirror存儲到棧幀中,也就是oop temp這個slot位置
0x00007fffe1014e6c: mov    %r11,0x10(%rbp)
// 將mirror拷到%rsi中作為靜態方法調用的第2個參數
0x00007fffe1014e70: lea    0x10(%rbp),%rsi
 

// **** L2 ****
// 不管是靜態還是非靜態方法,都會執行如下的彙編片段 

對於非靜態方法,如上彙編不會產生任何作用,對於靜態方法來說,會將靜態方法所在類對應的java.lang.Class對象存儲到0x10(%rbp)的位置,也就是棧幀中oop temp的位置。  

(4)連接Method::native_function並調用

// 獲取Method::native_function的地址並存儲到%rax中
0x00007fffe1014e74: mov    0x60(%rbx),%rax   
// %r11中存儲的是SharedRuntime::native_method_throw_unsatisfied_link_error_entry()
0x00007fffe1014e78: movabs $0x7ffff6a08f14,%r11 
// 判斷rax中的地址是否是native_method_throw_unsatisfied_link_error_entry的
// 地址,如果是說明本地方法未綁定
0x00007fffe1014e82: cmp    %r11,%rax
// 如果不等於,即native方法已經綁定,跳轉到----L3----
0x00007fffe1014e85: jne    0x00007fffe1014f1b 
 
 
// 執行這裡的程式碼,說明native方法沒有綁定,
// 調用InterpreterRuntime::prepare_native_call()函數重試,完成native方法綁定
0x00007fffe1014e8b: callq  0x00007fffe1014e95
0x00007fffe1014e90: jmpq   0x00007fffe1014f13
0x00007fffe1014e95: mov    %rbx,%rsi
0x00007fffe1014e98: lea    0x8(%rsp),%rax
0x00007fffe1014e9d: mov    %r13,-0x38(%rbp)
0x00007fffe1014ea1: mov    %r15,%rdi
0x00007fffe1014ea4: mov    %rbp,0x200(%r15)
0x00007fffe1014eab: mov    %rax,0x1f0(%r15)
0x00007fffe1014eb2: test   $0xf,%esp
0x00007fffe1014eb8: je     0x00007fffe1014ed0
0x00007fffe1014ebe: sub    $0x8,%rsp
0x00007fffe1014ec2: callq  0x00007ffff66af6aa
0x00007fffe1014ec7: add    $0x8,%rsp
0x00007fffe1014ecb: jmpq   0x00007fffe1014ed5
0x00007fffe1014ed0: callq  0x00007ffff66af6aa
0x00007fffe1014ed5: movabs $0x0,%r10
0x00007fffe1014edf: mov    %r10,0x1f0(%r15)
0x00007fffe1014ee6: movabs $0x0,%r10
0x00007fffe1014ef0: mov    %r10,0x200(%r15)
0x00007fffe1014ef7: cmpq   $0x0,0x8(%r15)
0x00007fffe1014eff: je     0x00007fffe1014f0a
0x00007fffe1014f05: jmpq   0x00007fffe1000420
0x00007fffe1014f0a: mov    -0x38(%rbp),%r13
0x00007fffe1014f0e: mov    -0x30(%rbp),%r14
0x00007fffe1014f12: retq   
// 結束__ call_VM()函數的調用
 
// 重新獲取Method*到%rbx中
0x00007fffe1014f13: mov    -0x18(%rbp),%rbx 
// 獲取native_function的地址拷到%rax中
0x00007fffe1014f17: mov    0x60(%rbx),%rax  
 
// **** L3 ****  

如上彙編為調用native_function本地函數準備了參數。此時的暫存器狀態如下:

%rbx:Method*  // 表示native方法的Method實例
%rax:native_function // 本地函數的指針
%rsi:mirro // 是靜態方法調用時的第2個參數
%rdi:JavaThread::jni_environment // 是為本地方法準備的第1個參數 

繼續看如下彙編:

// 將當前執行緒的JavaThread::jni_environment放入c_rarg0,也就是%rdi中
0x00007fffe1014f1b: lea    0x210(%r15),%rdi

// 將last_java_fp存儲到JavaThread::JavaFrameAnchor::last_java_fp
0x00007fffe1014f22: mov    %rbp,0x200(%r15)   
// 將last_java_pc存儲到JavaThead::JavaFrameAnchor::last_java_pc
0x00007fffe1014f29: movabs $0x7fffe1014f22,%r10 
0x00007fffe1014f33: mov    %r10,0x1f8(%r15)   
// 將last_java_sp保存到JavaThead.JavaFrameAnchor.last_java_pc中
0x00007fffe1014f3a: mov %rsp,0x1f0(%r15)
 
// 將執行緒的狀態改成_thread_in_native
0x00007fffe1014f41: movl   $0x4,0x288(%r15)   
// 調用native_function本地函數
0x00007fffe1014f4c: callq  *%rax              
 
 // 方法調用結束校驗或者恢復CPU控制狀態
0x00007fffe1014f4e: vzeroupper    
// 如下4行程式碼是為了保存調用native_function函數後得到的結果,將
// 結果存儲到棧頂
0x00007fffe1014f51: sub    $0x10,%rsp
0x00007fffe1014f55: vmovsd %xmm0,(%rsp)
0x00007fffe1014f5a: sub    $0x10,%rsp
0x00007fffe1014f5e: mov    %rax,(%rsp)

在調用完本地函數後會執行如下彙編:

// 改變執行緒的狀態為_thread_in_native_trans
0x00007fffe1014f62: movl   $0x5,0x288(%r15)
 
// 調用MacroAssembler::serialize_memory()函數
0x00007fffe1014f6d: mov    %r15d,%r11d
0x00007fffe1014f70: shr    $0x4,%r11d
0x00007fffe1014f74: and    $0xffc,%r11d
0x00007fffe1014f7b: movabs $0x7ffff7ff5000,%r10
// 結束MacroAssembler::serialize_memory()函數

在執行完如上的彙編程式碼後,我們已經執行完了native_function指向的本地函數,同時將本地函數的返回結果也存儲到了棧頂。

(5)對執行完native_function的安全點和異常的處理

繼續執行如下彙編:

// check for safepoint operation in progress and/or pending suspend requests
// 判斷安全點的狀態是否為_not_synchronized
0x00007fffe1014f85: mov    %r11d,(%r10,%r11,1)
0x00007fffe1014f89: cmpl   $0x0,0x1639454d(%rip)  # 0x00007ffff73a94e0
// 如果不相等,則處於安全點,跳轉到---- L ----
0x00007fffe1014f93: jne    0x00007fffe1014fa7  
 
// 判斷當前執行緒的suspend_flags是否為0,如果
// 是0則跳轉到---- Continue ----,表示沒有未處理的異常
0x00007fffe1014f99: cmpl $0x0,0x30(%r15)
0x00007fffe1014fa1: je     0x00007fffe1014fbd 
 
// **** L ****
// 執行這裡彙編時,說明處於安全點並且有未處理的異常

// 將JavaThread存儲到c_rarg0中
0x00007fffe1014fa7: mov    %r15,%rdi    
// 臨時將%rsp存儲到%r12中
0x00007fffe1014faa: mov    %rsp,%r12    
 // linux下不起作用
0x00007fffe1014fad: sub    $0x0,%rsp   
// 棧按16位元組對齊
0x00007fffe1014fb1: and    $0xfffffffffffffff0,%rsp 
// 調用JavaThread::check_special_condition_for_native_trans()函數
0x00007fffe1014fb5: callq  0x00007ffff6aaf360
// 恢復%rsp 
0x00007fffe1014fba: mov    %r12,%rsp    


// **** Continue ****

繼續執行如下彙編:

// 執行緒狀態調整為_thread_in_Java,表示running in Java or in stub code
0x00007fffe1014fbd: movl   $0x8,0x288(%r15) 
 
// 如下彙編清空JavaThead::JavaFrameAnchor::last_java_fp、last_java_sp與last_java_pc
0x00007fffe1014fc8: movabs $0x0,%r10
0x00007fffe1014fd2: mov    %r10,0x1f0(%r15)
0x00007fffe1014fd9: movabs $0x0,%r10
0x00007fffe1014fe3: mov    %r10,0x200(%r15)
0x00007fffe1014fea: movabs $0x0,%r10
0x00007fffe1014ff4: mov    %r10,0x1f8(%r15)
 
// 將JavaThread::active_handles(類型為JNIHandleBlock)存儲到%r11中
0x00007fffe1014ffb: mov    0x38(%r15),%r11
// 將JavaThread::active_handles::_top屬性置為NULL
0x00007fffe1014fff: movq   $0x0,0x108(%r11)

執行緒狀態在執行本地函數時的狀態為_thread_in_native,執行完成後更新為_thread_in_native_trans,最後更新為_thread_in_Java,也就表示回到了調用者Java方法,所以要清空本地函數使用的句柄,這樣其實就表示已經釋放掉了整個JavaThread::active_handles保存的單鏈接JNIHandleBlock中的所有句柄了,因為第1個JNIHandleBlock的_top屬性已經為0。這樣的操作會讓本地函數的局部對象引用全部變為無效狀態。

(6)對native_function執行的結果進行處理

繼續執行如下彙編程式碼:

// If result is an oop unbox and store it in frame where gc will see it
// and result handler will pick it up
// 從AbstractInterpreter::_native_abi_to_tosca數組中獲取對應返回類型的result_handler
0x00007fffe101500a: movabs $0x7fffe100ecdb,%r11

// 比較方法的結果處理程式result_handler是否是T_OBJECT類型的
0x00007fffe1015014: cmp    0x18(%rbp),%r11

// 如果不是則跳轉到----no_oop----
0x00007fffe1015018: jne    0x00007fffe101503e
// 如果是,先把棧頂的long類型的數據,即oop地址pop出來放到rax中
0x00007fffe101501e: mov    (%rsp),%rax
0x00007fffe1015022: add    $0x10,%rsp
0x00007fffe1015026: test   %rax,%rax

// 如果為0,跳轉到----store_result----
0x00007fffe1015029: je     0x00007fffe1015032 
// 如果不為0,那麼就表示有返回的oop,注意這裡的操作,因為本地函數返回的
// 是句柄,所以要從句柄中獲取到真正的oop地址
0x00007fffe101502f: mov    (%rax),%rax

// **** store_result ****

// 將%rax中的值存儲到棧的oop tmp中
0x00007fffe1015032: mov    %rax,0x10(%rbp)   
0x00007fffe1015036: sub    $0x10,%rsp
// 重新將%rax中的oop放到棧頂 
0x00007fffe101503a: mov    %rax,(%rsp)        
 
// **** no_oop ****

如上彙編程式碼對本地函數返回的結果進行了處理,尤其是當返回oop時,需要存儲到棧幀中開闢的oop temp這個slot中。

(7)判斷是否發生了棧溢出

繼續執行如下的彙編:

// 判斷當前執行緒的_stack_guard_state屬性是否是stack_guard_yellow_disabled,即是否發生了stack overflow
0x00007fffe101503e: cmpl   $0x1,0x2b4(%r15)
// 如果不等於,即沒有發生stack overflow,則跳轉到-- no_reguard --
0x00007fffe1015049: jne    0x00007fffe1015109
 
// 如果等,即發生stack overflow,則調用reguard_yellow_pages做必要的處理
0x00007fffe101504f: mov    %rsp,-0x28(%rsp)
0x00007fffe1015054: sub    $0x80,%rsp
0x00007fffe101505b: mov    %rax,0x78(%rsp)
0x00007fffe1015060: mov    %rcx,0x70(%rsp)
0x00007fffe1015065: mov    %rdx,0x68(%rsp)
0x00007fffe101506a: mov    %rbx,0x60(%rsp)
0x00007fffe101506f: mov    %rbp,0x50(%rsp)
0x00007fffe1015074: mov    %rsi,0x48(%rsp)
0x00007fffe1015079: mov    %rdi,0x40(%rsp)
0x00007fffe101507e: mov    %r8,0x38(%rsp)
0x00007fffe1015083: mov    %r9,0x30(%rsp)
0x00007fffe1015088: mov    %r10,0x28(%rsp)
0x00007fffe101508d: mov    %r11,0x20(%rsp)
0x00007fffe1015092: mov    %r12,0x18(%rsp)
0x00007fffe1015097: mov    %r13,0x10(%rsp)
0x00007fffe101509c: mov    %r14,0x8(%rsp)
0x00007fffe10150a1: mov    %r15,(%rsp)
0x00007fffe10150a5: mov    %rsp,%r12
0x00007fffe10150a8: sub    $0x0,%rsp
0x00007fffe10150ac: and    $0xfffffffffffffff0,%rsp
0x00007fffe10150b0: callq  0x00007ffff6a0e098
0x00007fffe10150b5: mov    %r12,%rsp
0x00007fffe10150b8: mov    (%rsp),%r15
0x00007fffe10150bc: mov    0x8(%rsp),%r14
0x00007fffe10150c1: mov    0x10(%rsp),%r13
0x00007fffe10150c6: mov    0x18(%rsp),%r12
0x00007fffe10150cb: mov    0x20(%rsp),%r11
0x00007fffe10150d0: mov    0x28(%rsp),%r10
0x00007fffe10150d5: mov    0x30(%rsp),%r9
0x00007fffe10150da: mov    0x38(%rsp),%r8
0x00007fffe10150df: mov    0x40(%rsp),%rdi
0x00007fffe10150e4: mov    0x48(%rsp),%rsi
0x00007fffe10150e9: mov    0x50(%rsp),%rbp
0x00007fffe10150ee: mov    0x60(%rsp),%rbx
0x00007fffe10150f3: mov    0x68(%rsp),%rdx
0x00007fffe10150f8: mov    0x70(%rsp),%rcx
0x00007fffe10150fd: mov    0x78(%rsp),%rax
0x00007fffe1015102: add    $0x80,%rsp
 
// **** no_guard ****

然後繼續執行如下程式碼:

 // 重新載入Method
0x00007fffe1015109: mov    -0x18(%rbp),%rbx  
0x00007fffe101510d: mov    0x10(%rbx),%r13
// 獲取ConstMethod::code的地址存儲到%r13中
0x00007fffe1015111: lea    0x30(%r13),%r13    

  

(8)處理異常

彙編程式碼如下:

// 判斷當前執行緒的_pending_exception屬性是否為空,即是否發生了異常
0x00007fffe1015115: cmpq   $0x0,0x8(%r15)
// 如果不為空,即沒有異常,跳轉到-- L --
0x00007fffe101511d: je     0x00007fffe101521f
 
// 當前執行緒的_pending_exception屬性不為空,表示發生了異常
// 省略調用__ MacroAssembler::call_VM()函數生成常式來調用InterpreterRuntime::throw_pending_exception()函數
// 省略調用__ should_not_reach_here()生成的彙編
 
// **** L **** 

  

(9)釋放鎖

彙編程式碼如下:

// 判斷目標方法是否是SYNCHRONIZED方法,如果是則需要解鎖,如果不是則跳轉到----L---- 
0x00007fffe101521f: mov    0x28(%rbx),%r11d
0x00007fffe1015223: test   $0x20,%r11d
// 不需要解鎖時直接跳轉即可
0x00007fffe101522a: je     0x00007fffe1015405 
 // 獲取偏向鎖BasicObjectLock的地址,存儲到c_rarg1中
0x00007fffe1015230: lea    -0x50(%rbp),%rsi
// 獲取偏向鎖的_obj屬性的地址
0x00007fffe1015234: mov    0x8(%rsi),%r11 
0x00007fffe1015238: test   %r11,%r11
// 判斷_obj屬性是否為空,如果不為空即未解鎖,跳轉到unlock完成解鎖
0x00007fffe101523b: jne    0x00007fffe101533d
 
// 如果已解鎖,說明鎖的狀態有問題,拋出異常
// 省略調用__ MacroAssembler::call_VM()函數生成的常式,這個常式用來調用InterpreterRuntime::throw_illegal_monitor_state_exception()函數
// 省略調用__ should_not_reach_here()生成的彙編
 
 
// 調用InterpreterMacroAssembler::unlock_object()函數 
// 將bcp保存到棧幀中
0x00007fffe101533d: mov    %r13,-0x38(%rbp) 
// %rsi中存儲的是BasicObjectLock,將BasicLock存儲到%rax
0x00007fffe1015341: lea    (%rsi),%rax 
0x00007fffe1015344: mov    0x8(%rsi),%rcx // 將_obj存儲到%rcx中
0x00007fffe1015348: movq   $0x0,0x8(%rsi) // 釋放_obj屬性
// 將_obj的markOop存儲到%rdx中
0x00007fffe1015350: mov    (%rcx),%rdx 
0x00007fffe1015353: and    $0x7,%rdx 
0x00007fffe1015357: cmp    $0x5,%rdx
// 如果已經是偏向狀態,則跳轉
0x00007fffe101535b: je     0x00007fffe1015401 

// 不為偏向狀態
// 將BasicLock中的markOop存儲到%rdx中
0x00007fffe1015361: mov    (%rax),%rdx
0x00007fffe1015364: test   %rdx,%rdx
// 如果為0,說明是鎖的重入,跳轉
0x00007fffe1015367: je     0x00007fffe1015401 
// 原子交換回原markOop,其中的%rdx中存儲的就是old markOop,而%rcx中存儲的是_obj
0x00007fffe101536d: lock cmpxchg %rdx,(%rcx) 
// 如果為0,說明是鎖的重入,跳轉
0x00007fffe1015372: je     0x00007fffe1015401  
// 執行這個彙編,說明為非鎖重入
0x00007fffe1015378: mov    %rcx,0x8(%rsi)  // restore obj
 
// 調用call_VM()函數來調用InterpreterRuntime::monitorexit()函數
0x00007fffe101537c: callq  0x00007fffe1015386  
0x00007fffe1015381: jmpq   0x00007fffe1015401
0x00007fffe1015386: lea    0x8(%rsp),%rax
0x00007fffe101538b: mov    %r13,-0x38(%rbp)
0x00007fffe101538f: mov    %r15,%rdi
0x00007fffe1015392: mov    %rbp,0x200(%r15)
0x00007fffe1015399: mov    %rax,0x1f0(%r15)
0x00007fffe10153a0: test   $0xf,%esp
0x00007fffe10153a6: je     0x00007fffe10153be
0x00007fffe10153ac: sub    $0x8,%rsp
0x00007fffe10153b0: callq  0x00007ffff66aaab2
0x00007fffe10153b5: add    $0x8,%rsp
0x00007fffe10153b9: jmpq   0x00007fffe10153c3
0x00007fffe10153be: callq  0x00007ffff66aaab2
0x00007fffe10153c3: movabs $0x0,%r10
0x00007fffe10153cd: mov    %r10,0x1f0(%r15)
0x00007fffe10153d4: movabs $0x0,%r10
0x00007fffe10153de: mov    %r10,0x200(%r15)
0x00007fffe10153e5: cmpq   $0x0,0x8(%r15)
0x00007fffe10153ed: je     0x00007fffe10153f8
0x00007fffe10153f3: jmpq   0x00007fffe1000420
0x00007fffe10153f8: mov    -0x38(%rbp),%r13
0x00007fffe10153fc: mov    -0x30(%rbp),%r14
0x00007fffe1015400: retq  
// 結束call_VM()調用
// 恢復bcp
0x00007fffe1015401: mov    -0x38(%rbp),%r13   
// 結束unlock_object()函數
 
 
// 省略調用notify_method_exit()生成的彙編

由於鎖的部分我們到目前為止還沒有介紹,所以這一塊我們暫時不詳細介紹,後面在介紹到鎖相關內容時,還會介紹這裡的內容。  

(10)收尾

彙編程式碼如下:

// restore potential result in edx:eax(表示64位數), call result handler to
// restore potential result in ST0 & handle result
// 將棧頂的代表方法調用結果的數據pop到%rax中
 
0x00007fffe101543c: mov    (%rsp),%rax
0x00007fffe1015440: add    $0x10,%rsp
0x00007fffe1015444: vmovsd (%rsp),%xmm0
0x00007fffe1015449: add    $0x10,%rsp
// 獲取result_handler存儲到%r11中
0x00007fffe101544d: mov    0x18(%rbp),%r11
 // 調用result_handler處理方法調用結果
0x00007fffe1015451: callq  *%r11          
 
// 獲取sender sp,開始恢復上一個Java棧幀
0x00007fffe1015454: mov    -0x8(%rbp),%r11 
// 相當於指令mov %ebp,%esp和pop %ebp
0x00007fffe1015458: leaveq               
 // 獲取return address
0x00007fffe1015459: pop    %rdi           
 // 設置sender sp
0x00007fffe101545a: mov    %r11,%rsp 
// 跳轉到返回地址處繼續執行     
0x00007fffe101545d: jmpq   *%rdi           

如果調用本地函數返回oop,則存儲到棧幀中的oop temp處,如果返回的是其它類型,如是浮點數存儲在%xmm0,整數等存儲在%rax中,這也是本地函數調用約定規定的,之前已經將%xmm0和%rax壓入了棧頂,現在恢復到相應的暫存器中,這樣就可以調用結果處理常式result_handler進行結果的處理了,處理完成後退棧,然後跳轉到返回地址處繼續執行即可。

基主要的執行流程如下圖所示。

2、設置解釋執行的入口

在Method::link_method()函數中為方法設置對應的入口entry,對於本地方法設置native入口,對於本地同步方法設置native_synchronized入口。函數的調用棧如下:

InstanceKlass::initialize_impl()  instanceKlass.cpp
InstanceKlass::link_class()       instanceKlass.cpp
InstanceKlass::link_class_impl()  instanceKlass.cpp 
InstanceKlass::link_class_impl()  instanceKlass.cpp 
InstanceKlass::link_methods()     instanceKlass.cpp
Method::link_method()             method.cpp

InstanceKlass::initialize_impl()函數在Java方法連接時會調用,這在《深入剖析Java虛擬機:源碼剖析與實例詳解(基礎卷)》中詳細介紹過。HotSpot VM對類進行連接時會調用Method::link_method()函數為Java方法設置執行入口。Method::link_method()函數的實現如下:

// 當方法所屬類連接時會調用如下函數,設置Java方法的執行入口,這樣Java方法就可以
// 解釋執行、編譯執行和動態分派了
void Method::link_method(methodHandle h_method, TRAPS) {
  // 方法入口已經設置完成,不需要重複設置
  if (_i2i_entry != NULL){
      return;
  }
 
  // 設置解釋執行的入口
  address entry = Interpreter::entry_for_method(h_method);
  // 將解釋執行的入口保存到_i2i_entry和_from_interpreted_entry
  set_interpreter_entry(entry);
 
  ...
  // 設置編譯執行的入口
  (void) make_adapters(h_method, CHECK); 
}

我們暫時不介紹編譯執行的入口,只看解釋執行相關內容。

方法連接主要就是做的事就是設置Method::_from_interpreted_entry屬性。連接過程主要是根據方法類型,獲取並保存方法對應的入口常式的地址到_i2i_entry和_from_interpreted_entry屬性中。

調用的Interpreter::entry_for_method()函數的實現如下:

static address entry_for_method(methodHandle m) {
      AbstractInterpreter::MethodKind mk = method_kind(m);
      return entry_for_kind(mk);
}

如上函數調用的method_kind()函數的實現中,與本地方法相關的邏輯如下: 

// 方法中有native關鍵字的就是native方法
if (m->is_native()) {  
    assert(!m->is_method_handle_intrinsic(), "overlapping bits here, watch out");
    return m->is_synchronized() ? native_synchronized : native;
}

在entry_for_method()函數中獲取MethodKind後調用entry_for_kind()函數,實現如下: 

static address entry_for_kind(MethodKind k) {
      assert(0 <= k && k < number_of_method_entries, "illegal kind");
      return _entry_table[k];
}

在函數TemplateInterpreterGenerator::generate_all()中會初始化_entry_table數組,TemplateInterpreterGenerator::generate_all()函數在HotSpot VM啟動時就會調用,所以_entry_table中保存的內容會在HotSpot VM啟動時就會設置。

在Method::link_method()函數中調用的set_interpreter_entry()函數的實現如下:

void set_interpreter_entry(address entry){
      _i2i_entry = entry;
      _from_interpreted_entry = entry;
}

 

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