我們寫的主類中的main()
方法是如何被Java
虛擬機調用到的?在Java
類中的一些方法會被由C/C++
編寫的HotSpot
虛擬機的C/C++函數調用,不過由于Java方法與C/C++函數的調用約定不同,所以并不能直接調用,需要JavaCalls::call()
這個函數輔助調用。(我把由C/C++編寫的叫函數,把Java編寫的叫方法,后續也會延用這樣的叫法)如下圖所示。
從C/C++函數中調用的一些Java方法主要有:
- (1)Java主類中的main()方法;
-
(2)Java主類裝載時,調用
JavaCalls::call()
函數執行checkAndLoadMain()
方法; -
(3)類的初始化過程中,調用
JavaCalls::call()
函數執行的Java類初始化方法<clinit>,可以查看JavaCalls::call_default_constructor()
函數,有對<clinit>方法的調用邏輯; -
(4)我們先省略main方法的執行流程(其實main方法的執行也是先啟動一個JavaMain線程,套路都是一樣的),單看某個
JavaThread
的啟動過程。JavaThread的啟動最終都要通過一個native方法java.lang.Thread#start0()
方法完成的,這個方法經過解釋器的native_entry入口,調用到了JVM_StartThread()
函數。其中的static void thread_entry(JavaThread* thread, TRAPS)
函數中會調用JavaCalls::call_virtual()
函數。JavaThread最終會通過JavaCalls::call_virtual()函數來調用字節碼中的run()方法; -
(5)在
SystemDictionary::load_instance_class()
這個能體現雙親委派的函數中,如果類加載器對象不為空,則會調用這個類加載器的loadClass()
函數(通過call_virtual()函數來調用)來加載類。
當然還會有其它方法,這里就不一一列舉了。通過JavaCalls::call()
、JavaCalls::call_helper()
等函數調用Java方法,這些函數定義在JavaCalls類中,
這個類的定義如下:
從C/C++函數中調用的一些Java方法主要有:
-
(1)Java主類中的
main()
方法; -
(2)Java主類裝載時,調用
JavaCalls::call()
函數執行checkAndLoadMain()
方法; -
(3)類的初始化過程中,調用
JavaCalls::call()
函數執行的Java類初始化方法<clinit>,可以查看JavaCalls::call_default_constructor()
函數,有對<clinit>方法的調用邏輯; -
(4)我們先省略main方法的執行流程(其實main方法的執行也是先啟動一個JavaMain線程,套路都是一樣的),單看某個
JavaThread
的啟動過程。JavaThread的啟動最終都要通過一個native方法java.lang.Thread#start0()
方法完成的,這個方法經過解釋器的native_entry入口,調用到了JVM_StartThread()
函數。其中的static void thread_entry(JavaThread* thread, TRAPS)
函數中會調用JavaCalls::call_virtual()
函數。JavaThread
最終會通過JavaCalls::call_virtual()
函數來調用字節碼中的run()方法; -
(5)在
SystemDictionary::load_instance_class()
這個能體現雙親委派的函數中,如果類加載器對象不為空,則會調用這個類加載器的loadClass()
函數(通過call_virtual()函數來調用)來加載類。
當然還會有其它方法,這里就不一一列舉了。通過JavaCalls::call()
、JavaCalls::call_helper()
等函數調用Java方法,這些函數定義在JavaCalls類中,
這個類的定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
源代碼位置:openjdk/hotspot/src/share/vm/runtime/javaCalls.hpp class JavaCalls: AllStatic { static void call_helper(JavaValue* result, methodHandle* method, JavaCallArguments* args, TRAPS); public : static void call_default_constructor(JavaThread* thread, methodHandle method, Handle receiver, TRAPS); // 使用如下函數調用Java中一些特殊的方法,如類初始化方法<clinit>等 // receiver表示方法的接收者,如A.main()調用中,A就是方法的接收者 static void call_special(JavaValue* result, KlassHandle klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS); static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS); static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_special(JavaValue* result, Handle receiver, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); // 使用如下函數調用動態分派的一些方法 static void call_virtual(JavaValue* result, KlassHandle spec_klass, Symbol* name,Symbol* signature, JavaCallArguments* args, TRAPS); static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, TRAPS); static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_virtual(JavaValue* result, Handle receiver, KlassHandle spec_klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); // 使用如下函數調用Java靜態方法 static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, JavaCallArguments* args, TRAPS); static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, TRAPS); static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, TRAPS); static void call_static(JavaValue* result, KlassHandle klass,Symbol* name, Symbol* signature, Handle arg1, Handle arg2, TRAPS); // 更低一層的接口,如上的一些函數可能會最終調用到如下這個函數 static void call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS); }; |
如上的函數都是自解釋的,通過名稱我們就能看出這些函數的作用。其中JavaCalls::call()
函數是更低一層的通用接口。Java虛擬機規范定義的字節碼指令共有5個,分別為invokestatic
、invokedynamic
、invokestatic
、invokespecial
、invokevirtual
幾種方法調用指令。這些call_static()
、call_virtual()
函數內部調用了call()函數。這一節我們先不介紹各個方法的具體實現。下一篇將詳細介紹。
我們選一個重要的main()
方法來查看具體的調用邏輯。如下基本照搬R大的內容,不過我略做了一些修改,如下:
假設我們的Java主類的類名為JavaMainClass
,下面為了區分java launcher
里C/C++的main()
與Java
層程序里的main(),把后者寫作JavaMainClass.main()方
法。
從剛進入C/C++的main()函數開始:
啟動并調用HotSpot虛擬機的main()函數的線程執行的主要邏輯如下:
1
2
3
|
main() -> //... 做一些參數檢查 -> //... 開啟新線程作為main線程,讓它從JavaMain()函數開始執行;該線程等待main線程執行結束 |
在如上線程中會啟動另外一個線程執行JavaMain()函數,如下:
1
2
3
4
5
6
|
JavaMain() -> //... 找到指定的JVM -> //... 加載并初始化JVM -> //... 根據Main-Class指定的類名加載JavaMainClass -> //... 在JavaMainClass類里找到名為"main"的方法,簽名為"([Ljava/lang/String;)V",修飾符是public的靜態方法 -> (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); // 通過JNI調用JavaMainClass.main()方法 |
以上步驟都還在java launcher
的控制下;當控制權轉移到JavaMainClass.main()
方法之后就沒java launcher
什么事了,等JavaMainClass.main()
方法返回之后java launcher
才接手過來清理和關閉JVM。
下面看一下調用Java主類main()
方法時會經過的主要方法及執行的主要邏輯,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// HotSpot VM里對JNI的CallStaticVoidMethod的實現。留意要傳給Java方法的參數 // 以C的可變長度參數傳入,這個函數將其收集打包為JNI_ArgumentPusherVaArg對象 -> jni_CallStaticVoidMethod() // 這里進一步將要傳給Java的參數轉換為JavaCallArguments對象傳下去 -> jni_invoke_static() // 真正底層實現的開始。這個方法只是層皮,把JavaCalls::call_helper() // 用os::os_exception_wrapper()包裝起來,目的是設置HotSpot VM的C++層面的異常處理 -> JavaCalls::call() -> JavaCalls::call_helper() -> //... 檢查目標方法是否為空方法,是的話直接返回 -> //... 檢查目標方法是否“首次執行前就必須被編譯”,是的話調用JIT編譯器去編譯目標方法 -> //... 獲取目標方法的解釋模式入口from_interpreted_entry,下面將其稱為entry_point -> //... 確保Java棧溢出檢查機制正確啟動 -> //... 創建一個JavaCallWrapper,用于管理JNIHandleBlock的分配與釋放, // 以及在調用Java方法前后保存和恢復Java的frame pointer/stack pointer //... StubRoutines::call_stub()返回一個指向call stub的函數指針, // 緊接著調用這個call stub,傳入前面獲取的entry_point和要傳給Java方法的參數等信息 -> StubRoutines::call_stub()(...) // call stub是在VM初始化時生成的。對應的代碼在 // StubGenerator::generate_call_stub()函數中 -> //... 把相關寄存器的狀態調整到解釋器所需的狀態 -> //... 把要傳給Java方法的參數從JavaCallArguments對象解包展開到解釋模 // 式calling convention所要求的位置 -> //... 跳轉到前面傳入的entry_point,也就是目標方法的from_interpreted_entry -> //... 在-Xcomp模式下,實際跳入的是i2c adapter stub,將解釋模式calling convention // 傳入的參數挪到編譯模式calling convention所要求的位置 -> //... 跳轉到目標方法被JIT編譯后的代碼里,也就是跳到 nmethod 的 VEP 所指向的位置 -> //... 正式開始執行目標方法被JIT編譯好的代碼 <- 這里就是"main()方法的真正入口" |
后面3個步驟是在編譯執行的模式下,不過后續我們從解釋執行開始研究,所以需要為虛擬機配置-Xint選項,有了這個選項后,Java主類的main()
方法就會解釋執行了。
在調用Java主類main()
方法的過程中,我們看到了虛擬機是通過JavaCalls::call()
函數來間接調用main()
方法的,下一篇我們研究一下具體的調用邏輯。
到此這篇關于關于Java虛擬機HotSpot的文章就介紹到這了,更多相關Java虛擬機HotSpot內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://www.heapdump.cn/article/2860175