JVM學習(五) -執行子系統

IT迷途小書童發表於2020-11-09

虛擬機器和物理機的區別。兩種都有程式碼執行能力。物理機的執行引擎是建立在處理器、硬體、指令集和作業系統上。而虛擬機器的執行引擎是有自己實現的。因此可以自行的制定指令集和執行引擎的結構關係。

個人理解:分為三個部分。分別是介紹執行時分的各個記憶體區域【程式計數器、java虛擬機器棧【區域性變數表、運算元棧、動態連結、方法返回地址】、本地方法棧、方法區、堆】。方法的呼叫【解析、分派】、方法的執行【解析執行、編譯執行】

1. 執行時棧幀結構

棧幀是用於虛擬機器進行方法呼叫和方法執行的資料結構。是虛擬機器棧的棧元素。這部分內容和執行記憶體區域中JAVA虛擬機器棧中的棧幀結構部分有重疊的部分。一個棧幀包括區域性變數表、運算元棧、動態連結和方法返回地址四部分。棧幀需要多大的區域性變數表和多深的運算元棧在編譯階段都已經確定的

Java虛擬機器的解釋執行引擎稱為“基於棧的執行引擎”,其中的棧指的是運算元棧。

程式計數器、虛擬機器棧、本地方法棧都是執行緒級別的。跟著現成的生而生、跟著執行緒的滅而滅。意味著,每個執行緒都有自己的java虛擬機器棧。該執行緒中呼叫的方法都作為一個棧幀,壓棧和彈棧。

(1) 區域性變數表

主要用來儲存方法的引數和方法內部定義的的區域性變數。區域性變數表是一組值儲存空間。

一個方法只有在呼叫完成後才會釋放。因而,如果一個方法之前定義了大記憶體的物件,但是,在方法後半部分已經不需要這部分記憶體了。同時,後半部分需要執行很長時間。這種情況下,會造成前半部分建立的大容量的物件既不被使用需要,又因為方法因為沒有執行完成而釋放。這部分記憶體會一致佔著不能被釋放。所以,也可以在方法內,將不需要繼續使用的變數賦值為null。可以讓垃圾回收器及時的回收。

(2) 運算元棧

運算元棧的深度在編譯的時候已經確定。在方法執行的過程中,從區域性變數表中獲取資料,壓棧。需要計算的時候根據運算子號從棧頂彈棧獲得資料進行計算,將結果壓棧。

(3) 動態連結

class檔案中,一個方法需要呼叫其它方法。需要將這些方法的符號引用轉換成記憶體地址中的直接引用。而這些符號引用存在方法區的常量池中

每個棧的棧幀都包含一個指向執行時常量池中該方法的符號引用。持有這個引用的目的是支援方法呼叫中的動態連結。

這些符號引用一部分會在類載入階段或者第一次使用的時候轉換成了直接引用。這種轉換稱為靜態解析。另一部分將在每一次執行期間轉化成直接引用。這部分稱為動態連結。

(4) 方法返回地址

當一個方法開始執行時,可能有兩種方式退出該方法正常完成出口異常完成出口。

無論方法採用何種方式退出,在方法退出後都需要返回到方法被呼叫的位置,程式才能繼續執行,方法返回時可能需要在當前棧幀中儲存一些資訊,用來幫他恢復它的上層方法執行狀態。

2. 方法呼叫

方法呼叫並不等同於方法執行,核心任務是:確定被呼叫方法的版本【即呼叫的是哪一個方法】。不涉及方法內部的具體執行執行過程。

所有方法之間的呼叫在class檔案中儲存的是符號引用。而不是具體方法【實際執行時記憶體佈局的入口地址】的直接引用。需要在類載入甚至在執行期間才能確定目標方法的直接引用。

(1) 解析

呼叫的目標方法都是方法區的常量池中的一個符號引用。如果程式在真正執行之前就可以確定方法呼叫的版本,並且這個方法呼叫的版本在執行期間是不會發生變化的。這種情境下,在類載入的解析階段,會將符號引用轉換成直接引用。這種方法的呼叫稱之為解析。

符合“編譯期可知,執行期不可變”的要求的方法,主要包括靜態方法和私有方法兩大類。前者與類直接關聯,後者外部不可被訪問。他們特點是不可能通過繼承或者其它方式重寫其它版本。因此,都會通過解析的方式進行方法呼叫。

解析對應5條方法呼叫指令:

① Invoke static :呼叫靜態方法

② Invoke special:呼叫例項構造器<init>方法,私有方法和父類方法。

③ Invoke virtual:呼叫所有的虛方法。【final修飾的方法,因為不能被覆蓋,沒有其它版本,所以,無需對方法進行多型選擇】

④ Invoke Interface:呼叫介面方法(多型)

⑤ Invoke dynamic

解析是一個靜態的過程,在類載入的連結階段的解析階段中,就會把涉及的符號引全部轉換成直接引用。

(2) 分派

① 靜態分派

所有依賴靜態型別【引數的宣告型別來定位方法版本的分派稱為靜態分派。

靜態分派最典型的是過載。在編譯階段,根據引數的靜態型別就可以確定執行的是哪個版本的方法。靜態分派發生在編譯階段,因此,靜態分配的動作不由虛擬機器來執行。

② 動態分派

執行期間根據實際型別確定方法執行的版本的分派稱為動態分派。

動態分派最典型的就是重寫

JVMinvoke virtual指令了,這個指令的解析過程有助於我們更深刻理解重寫的本質。該指令的具體解析過程如下

  1. 找到運算元棧棧頂的第一個元素所指向的物件的實際型別,記為C。【獲得實際物件的型別】。
  2. 如果在型別C中找到與常量中描述符和簡單名稱都相符的方法,則進行訪問許可權的校驗,如果通過則返回這個方法的直接引用,查詢結束;如果不通過,則返回非法訪問異常【在C中找和當前方法匹配的方法】
  3. 如果在型別C中沒有找到,則按照繼承關係從下到上依次對C的各個父類進行第2步的搜尋和驗證過程首先在子類中找,沒有再在父類中找。和雙親委派相反,雙親委派優先在父類載入器中處理,而過載優先在子類中匹配
  4. 如果始終沒有找到合適的方法,則丟擲抽象方法錯誤的異常

③ 單分派和多分派

(3) 動態型別語言支援

3. 執行引擎

核心理解虛擬機器如果執行方法中的位元組碼指令集。其實就是基於棧的位元組碼解釋執行引擎做的事情。JAVA虛擬機器執行程式碼的時候,都有解釋執行【通過直譯器執行】編譯執行【通過編譯器產生原生程式碼執行】兩種選擇

執行引擎:將位元組碼指令解析/編譯為對應平臺上本地機器指令。簡而言之,執行引擎充當高階語言和機器碼之間的翻譯者。

其中,執行引擎中 直譯器(interpreter)提供解釋執行功能。JIT Completer 提供編譯執行的功能。

 

 

 

(1) 解釋執行

JVM得到位元組碼之後。通過解析器Interpreter解析成最終的機器碼。

(2) 基於棧的指令集和基於暫存器的指令集

基於棧的指令集:將指令集儲存在棧中。大部分是零地址指令集,依賴運算元棧進行工作。Java編譯器輸出的指令集是基於棧的指令集。

PC電腦支援的就是基於暫存器的指令集

(3) 基於棧的直譯器執行過程

從指令集順序執行,將區域性變數經過運算元棧,最後在區域性變數表中生成。運算的過程是從區域性變數表獲得資料,然後放入運算元棧。從運算元棧彈出進行計算。之後將結果壓棧。

 

先將數放入運算元棧,然後將運算元棧中的資料彈出放入區域性變數表中,這樣一步步將區域性變數表的變數賦值。運算過程中,從區域性變數表中獲取資料壓入運算元棧中。然後根據指令,從運算元棧中彈出資料進行計算,之後將計算結果壓棧。

(4) 編譯執行

JVM平臺提供一種及時編譯技術,及時編譯的目的是避免函式被解釋執行。而是將整個函式體編譯成機器碼。每次函式執行時,直接執行編譯後的機器碼。這種方式能將效率大幅提升。

 

相關文章