JVM系列-字節碼執行

這篇文章對應於java虛擬機的第8章,在學習虛擬機的時候,讀完類加載和字節碼執行這幾章節,感覺自己沒有理解透徹,整體流程也沒有梳理清楚。最後又讀了一遍,才把思路梳理清楚。看書還是要站在一定的高度去看,一開始看的時候,只是學到了一個個的知識點,單獨看一個知識點倒是知道什麼意思,但是看完之後,依然不清楚整體的流程。

這篇文章需要分成三部分來說,第一部分是背景知識,第二部分是方法調用,第三部分是方法執行。
背景知識是說一下方法調用和方法執行依賴的數據結構,也就是棧幀。
方法調用是確定被調用方法的版本,也就是到底要調用哪個方法,不涉及方法內部的具體運行過程。
方法執行是虛擬機執行方法中的字節碼指令的過程,也就是解釋執行和編譯執行。

所以,通俗的講,這篇文章的內容,就是去說明虛擬機是如何選擇要調用哪個方法,以及如何執行這些方法的。不過在這篇文章中,偏向於對整個流程進行一個梳理和總結,細節上涉及的可能會比較少。

一、背景知識:
棧幀是支持虛擬機進行方法調用和方法執行的數據結構,在JVM內存中,是虛擬機棧的棧元素,虛擬機棧是線程私有的。
一個方法從開始調用到執行完成,就對應這一個棧幀在虛擬機棧中入棧到出棧的過程。棧幀存儲了方法的局部變量表、操作數棧、動態連接和方法返回地址等信息。

除了棧幀之外,還需要知道在Java虛擬機裏面進行方法調用的這5條字節碼指令:
invokestatic:調用靜態方法。
invokespecial:調用實例構造器<init>方法、私有方法和父類方法。
invokevirtual:調用所有的虛方法。
invokeinterface:調用接口方法,會在運行時再確定一個實現此接口的對象。
invokedynamic:調用動態方法,先在運行時動態解析出調用點限定符所引用的方法,然後再執行該方法,在此之前的4條調用指令,分派邏輯是固化在Java虛擬機內部的,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。

在編譯過程中,虛擬機並不知道目標方法的具體內存地址。因此,Java 編譯器會暫時用符號引用來表示該目標方法。這一符號引用包括目標方法所在的類或接口的名字,以及目標方法的方法名和方法描述符。

二、方法調用:
這裏說的方法調用,其實就是把class文件的符號引用轉換爲直接引用的過程,也就是確定調用哪個方法。
在編譯後的class文件中,對調用的方法都是存儲的符號引用,而不是方法實際的內存地址。需要在類加載期間,甚至是運行期間,才能確定目標方法的直接引用。
在類加載階段,會把一部分符號引用轉換爲直接引用,轉換的前提是方法在真正運行之前就有一個可確定的調用版本,並且這個版本在運行期是不可變的。在Java中,符合這種要求的方法名主要就是靜態方法和私有方法,靜態方法直接與類關聯,私有方法在外部不可以被訪問,也不可以被繼承,這兩種方法都不會存在其他版本,可以在類加載階段進行解析。被invokestatic和invokespecial指令調用的方法,都可以在解析階段確定唯一調用版本,所以,除了靜態方法和私有方法之外,實例構造器和父類方法也可以在類加載階段把符號引用解析爲直接引用。
而對於那些虛方法(final的除外),比如那些公有的實例方法,或者接口方法,這種方法調用是通過invokevirtual和invokeinterface指令調用的。對invokevirtual指令來說,調用需要先根據棧幀中的操作數棧存放的對象的實際類型來查找方法,所以這種就是在運行期間才能確定方法的直接引用。
所以,虛擬機把符號引用替換爲直接引用的過程,在類加載階段和運行階段都會發生。
那接下來還有個問題,虛擬機是怎麼區分方法的呢?
虛擬機識別方法的關鍵在於類名、方法名以及方法描述符(method descriptor)。方法描述符是由方法的參數類型以及返回類型所構成。這裏就和Java編譯器不一樣了,因爲虛擬機會把方法的返回類型作爲判斷條件,方法名相同,入參相同但返回值不同的方法,對虛擬機來說就是兩個方法。但是這種方法在Java編譯器就會報錯了。

說完了這些,再聊下我們常見的重載和重寫,這種方法是如何進行選擇和調用的。
先說個定義,對於 Animal a = new Dog(); 這句代碼來說,a是變量,Animal是這個變量的靜態類型,Dog是這個變量的實際類型。
對於方法重載,在編譯階段,Java編譯器就會根據參數的靜態類型決定使用哪個重載的版本。所以,準確的來說,這個選擇和虛擬機並沒有關係。在把代碼編譯爲.class文件的時候,就已經選擇好了。
對於方法重寫,則是運行時根據對象的實際類型來查找對應的方法,所以是在運行期間才把符號引用替換爲直接引用的。

三、方法執行:
獲取到字節碼指令後,虛擬機就需要執行這些指令了。從硬件視角來看,Java字節碼無法直接執行,需要Java虛擬機將字節碼翻譯爲機器碼纔行。而虛擬機把字節碼翻譯爲機器碼有兩種方式:解釋執行和編譯執行。

解釋執行:通過解釋器,逐條將字節碼翻譯成機器碼。
編譯執行:通過即時編譯器,將一個方法中包含的所有字節碼編譯成機器碼,並進行個各種層次的優化。

解釋器與編譯器兩者各有優勢:當程序需要迅速啓動和執行的時候,解釋器可以首先發揮作用,省去編譯的時間,立即執行。在程序運行後,隨着時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼之後,可以獲取更高的執行效率。當程序運行環境中內存資源限制較大(如部分嵌入式系統中),可以使用解釋執行節約內存,反之可以使用編譯執行來提升效率

HotSpot 默認採用混合模式,綜合瞭解釋執行和即時編譯兩者的優點。它會先解釋執行字節碼,而後將其中反覆執行的熱點代碼,以方法爲單位進行即時編譯。

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