總結call_stub()中的幾個重要參數

      接着上一篇日誌,先回顧下Java主函數調用時必須執行的call_stub函數:

static CallStub call_stub()
{
    return (CallStub)(castable_address(_call_stub_entry);
}

      castable_address是一個函數,展開後返回的是address_word類型,其最終原型就是unsigned int,_call_stub_entry也是unsigned int類型,指向了某個內存地址。返回值最終會被類型轉換爲CallStub類型的函數指針,總的來說,call_stub()函數的調用邏輯就是,在JVM初始化時,便會讓_call_stub_entry(unsigned int類型) 指向某一內存區域,接着類型轉換成CallStub自定義的函數指針,最後返回,JVM將call_stub()的返回值函數指針作爲函數調用。CallStub的結構上一篇日誌有寫,一個有八個參數,其中result_val_address返回值地址,result_type返回值類型和size_of_parameters入參數量,這些比較簡單,而link連接器、Java方法對象method()、調用Java方法的入口函數entry_point以及方法參數集合parameters等對於JVM調用Java方法來說十分重要。

 

link連接器

      先來看看link類型的結構,連接器起到連接的作用,建立調用函數和被調用函數之間的連接,例如main()函數裏調用了run()函數,則連接器爲其建立連接,讓它們之間可以完成傳參,入參,返回等操作。

class JavaCallWrapper: StackObj {
	friend class VMStruct;
	private:
	JavaThread*	_thread;
	JNIHandleBlock*	_handles;
	methodOop	_callee_method;
	oop	_receiver;
	JavaFrameAnchor	_anchor;
	JavaValue*	_result;
};

      JavaThread標識的顯然就是當前Java方法線程,_handles表示的是調用句柄,句柄就是Java中的引用,這裏不詳細解釋。_callee_method指的是調用者的方法對象,_receiver是被調用者,_anchor,線程堆棧,_result很容易知道就是方法的返回值。通過link建立連接後的調用者和被調用這,例如main()函數裏調用run()函數,main()函數可以在自己的棧空間中把參數壓棧,run()函數可以從main()函數的棧中讀參入參。

 

method()方法對象

      method()方法對象表示的是當前調用的Java方法在JVM內部建立的函數模型,這個模型包含了Java方法的全部信息,例如方法名、入參類型、入參數量、編譯後的字節碼指令,註解和返回信息等。JVM在調用call_stub()函數執行某一Java方法時,會通過method()對象得到Java方法編譯後的字節碼,得到字節碼後JVM才能對其進行解釋執行。看到這裏你可能會想,僅僅爲了得到字節碼來解釋運行,爲什麼還要建立整個Java方法的函數模型,存儲這麼多待執行方法的信息呢?直接要字節碼不就夠了嗎?對的,不過大家還記得Java中提供的反射機制嗎?反射對象可以獲得一個Java類的信息,包括類中的方法信息,用的是Class類,隨便找一個反射例子:

      例子中有一個ReenterLock類,類中有run()方法和main()方法,在main()方法程序運行時,先實例化ReenterLock類的一個實例對象L1,然後通過Class.forName().getMethods()方法等,可以獲得類中包含的方法,直接調用類實例對象的getClass()方法可以獲得該類的一個“模板”,輸出它的類名等信息:

      這就是Java的反射機制,之所以反射機制能夠獲得Java類,類中方法等各種信息,就是因爲上面說到的,JVM在內部爲每一個類,Java方法都建立了函數模型,使得在程序運行過程中還可以動態獲取類的信息,所以,method()對象做那麼多事情,保存那麼多Java方法的信息,除了提供字節碼給JVM解釋執行外,還有其他重要用途。

 

parameters()入參信息

      parameters顧名思義就是Java方法的入參,JVM在call_stub()執行過程中調用CallStub函數指針,通過parameters參數解析出入參信息,再根據size_of_parameters(),即參數數量,來爲方法分配棧空間,最後將參數逐個壓棧。這裏有一點要注意的是,在JVM的棧內存模型中,存放的只是變量的引用(也就是地址),而不是數據,這樣做的好處是節省了很大的棧內存空間,前面日誌寫彙編函數調用過程中的棧分配時,可以看到,棧分配的大小並不大,通常爲幾MB,如果我們把棧空間分配的很大,當然可以,但是如果JVM爲每一個方法棧都分配較大的空間,遇到遞歸調用之類的,或是方法裏調用深度很深的情況,那麼棧空間將會消耗掉很大的內存,容易造成內存溢出問題,這也是爲什麼JVM選擇在方法棧空間中保存變量,例如Java類的實例對象時,存放的是其引用。

      CallStub函數指針中的parameters()參數描述了Java方法的入參信息,例如名字,類型,size_of_parameters()標識了參數的數量,結合起來,因爲JVM內存模型中存放的是參數的引用,只有知道了參數類型,還有參數數量,最後才能計算出方法棧需要的空間大小。

 

entry_point入口

      entry_point參數可以說是CallStub函數指針中最重要的參數了,它表示的是JVM調用Java方法的入口,凡是調用Java方法,都要先經過該入口,執行這段機器指令,之後才能跳轉到Java方法對應的機器指令執行,該入口的機器指令在JVM初始化時就會生成。entry_point做的事情是從method()中獲取執行Java方法的字節碼,JVM通過其獲得需要執行的方法第一個字節碼後,就可以開始解釋執行:

      可以看到,JVM在調用某一Java方法時,先通過調用CallStub函數指針,在函數指針中,所調用的函數,也就是地址,就是_call_stub_entry,前面說過,它是一個unsigned int類型,保存的是地址,JVM通過其所存放的函數地址,去調用函數。但是現在JVM只是找到了待調用函數的地址,也就是它在哪個位置,但是函數的可執行字節碼還沒有,所以在此之前還需要通過entry_point例程入口,得到待執行函數的字節碼指令,最後才能真正地執行函數,即我們的Java方法。由上圖看到_call_stub_entry和entry_point都是例程類型入口,例程就是JVM爲我們寫好的一些邏輯處理指令,例如函數調用和返回,異常處理等,因爲Java是高級語言,需要解釋成機器能讀懂執行的語言,就需要通過這些中間方幫忙,還記得前幾篇日誌裏寫的JVM執行引擎嗎?就是用的C語言實現,體現如何將Java這類高級語言翻譯成機器指令,讓CPU執行。這些例程都是JVM在初始化時就爲我們寫好了的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章