第1篇-關於JVM運行時,開篇說的簡單些

開講Java運行時,這一篇講一些簡單的內容。咱們寫的主類中的main()方法是如何被Java虛擬機調用到的?在Java類中的一些方法會被由C/C++編寫的HotSpot虛擬機的C/C++函數調用,不過因爲Java方法與C/C++函數的調用約定不一樣,因此並不能直接調用,須要JavaCalls::call()這個函數輔助調用。(我把由C/C++編寫的叫函數,把Java編寫的叫方法,後續也會延用這樣的叫法)以下圖所示。java

 從C/C++函數中調用的一些Java方法主要有:微信

(1)Java主類中的main()方法;app

(2)Java主類裝載時,調用JavaCalls::call()函數執行checkAndLoadMain()方法;函數

(3)類的初始化過程當中,調用JavaCalls::call()函數執行的Java類初始化方法<clinit>,能夠查看JavaCalls::call_default_constructor()函數,有對<clinit>方法的調用邏輯;oop

(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類中,這個類的定義以下:對象

源代碼位置:/src/share/vm/runtime/javaCalls.hpp
// All calls to Java have to go via JavaCalls. Sets up the stack frame
// and makes sure that the last_Java_frame pointers are chained correctly.
 
class JavaCalls: AllStatic {
  static void call_helper(JavaValue* result, methodHandle* method, JavaCallArguments* args, TRAPS);
 public:
  // Optimized Constuctor call
  static void call_default_constructor(JavaThread* thread, methodHandle method, Handle receiver, TRAPS);
 
  // call_special
  // ------------
  // The receiver must be first oop in argument list
  // 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); // No args
  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);
 
  // virtual call
  // ------------
 
  // The receiver must be first oop in argument list
  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); // No args
  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);
 
  // Static call
  // -----------
  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);
 
  // Low-level interface
  static void call(JavaValue* result, methodHandle method, JavaCallArguments* args, TRAPS);
};

能夠看出,JavaCalls::call()函數爲虛擬機調用Java方法提供了便利。如上的函數都是自解釋的,對應各自的invoke*指令,Java虛擬機有invokestatic、invokedynamic、invokestatic、invokespecial、invokevirtual幾種方法調用指令。這些call_static()、call_virtual()函數內部調用了call()函數。這一節咱們先不介紹各個方法的具體實現。下一篇將詳細介紹。  blog

咱們選一個重要的main()方法來查看具體的調用邏輯。以下基本照搬R大的內容,不過我略作了一些修改,以下:ci

假設咱們的Java主類的類名爲JavaMainClass,下面爲了區分java launcher裏C/C++的main()與Java層程序裏的main(),把後者寫做JavaMainClass.main()方法。
從剛進入C/C++的main()函數開始:

啓動並調用HotSpot虛擬機的main()函數的線程:

main()函數
-> //... 作一些參數檢查
-> //... 開啓新線程做爲main線程,讓它從JavaMain()開始執行;該線程等待main線程執行結束  

在如上線程中會啓動另一個線程執行JavaMain()函數,以下:

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()函數時會通過的主要方法及執行的主要邏輯,以下:

// 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()( ... )  //... StubRoutines::call_stub()返回一個指向call stub的函數指針,緊接着調用這個call stub,傳入前面獲取的entry_point和要傳給Java方法的參數等信息
                 // 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()方法的,下一篇咱們研究一下具體的調用邏輯。

搭建過程當中若是有問題可直接評論留言或加做者微信mazhimazh。

關注公衆號,有HotSpot源碼剖析系列文章!

  

相關文章
相關標籤/搜索