要點提煉| 理解JVM之字節碼執行引擎

本篇將從概念模型的角度來介紹虛擬機的方法調用和字節碼執行。

  • 概述
  • 運行時棧幀結構
  • 方法調用

1.概述

a.有關虛擬機與物理機的異同

  • 同:都有代碼執行能力
  • 異:
    • 物理機的執行引擎是直接建立在處理器、硬件、指令集和操作系統層面上的
    • 虛擬機的執行引擎是由自定義的,可自行制定指令集與執行引擎的結構體系,且能夠執行不被硬件直接支持的指令集格式

b.有關Java虛擬機字節碼執行引擎的概念模型

  • 從外觀上,所有Java虛擬機的執行引擎都是一致的。輸入的是字節碼文件,處理的是字節碼解析的等效過程,輸出的是執行結果
  • 從實現上,執行引擎有多種執行Java代碼的選擇
    • 解釋執行:通過解釋器執行
    • 編譯執行:通過即時編譯器產生本地代碼執行
    • 兩者兼備,甚至還會包含幾個不同級別的編譯器執行引擎

2.運行時棧幀結構

  • 棧幀(Stack Frame):用於支持虛擬機進行方法調用和方法執行的數據結構,是虛擬機運行時數據區中的虛擬機棧的棧元素
  • 存儲內容:方法的局部變量表、操作數棧、動態連接、方法返回地址一些額外的附加信息
  • 每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
  • 一個棧幀需要分配多少內存在程序編譯期就已確定,而不會受到程序運行期變量數據的影響
  • 對於執行引擎來說,只有位於棧頂的棧幀(當前棧幀)纔是有效的,即所有字節碼指令只對當前棧幀進行操作,與當前棧幀相關聯的方法稱爲當前方法
  • 在概念模型上,典型的棧幀結構如圖:

接下來詳細介紹棧幀中各個部分的作用和數據結構

a.局部變量表

  • 局部變量表(Local Variable Table)是一組變量值存儲空間
  • 作用:存放方法參數和方法內部定義的局部變量
  • 分配時期:Java程序編譯爲Class文件時,會在方法的Code屬性的max_locals數據項中確定了該方法所需要分配的局部變量表的最大容量
  • 最小單位:變量槽
    • 大小:虛擬機規範中沒有明確指明一個變量槽佔用的內存空間大小,允許變量槽長度隨着處理器、操作系統或虛擬機的不同而發生變化
      • 對於32位以內的數據類型(boolean、byte、char、short、int、float、reference、returnAddress ),虛擬機會爲其分配一個變量槽空間
      • 對於64位的數據類型(long、double ),虛擬機會以高位對齊的方式爲其分配兩個連續的變量槽空間
    • 特點:可重用。爲了儘可能節省棧幀空間,若當前字節碼PC計數器的值已超出了某個變量的作用域,則該變量對應的變量槽可交給其他變量使用
  • 訪問方式:通過索引定位。索引值的範圍是從0開始至局部變量表最大的變量槽數量

b.操作數棧

  • 操作數棧(Operand Stack)是一個後入先出棧
  • 作用:在方法執行過程中,寫入(進棧)和提取(出棧)各種字節碼指令
  • 分配時期:同樣的,在編譯時會在方法的Code屬性的max_locals數據項中確定操作數棧的最大深度(在方法執行的任何時候,操作數棧的深度都不會超過在max_stacks數據項中設定的最大值)
  • 棧容量:操作數棧的每一個元素可以是任意的Java數據類型——32位數據類型所佔的棧容量爲1,64位數據類型所佔的棧容量爲2
  • 注意:操作數棧中元素的數據類型必須與字節碼指令的序列嚴格匹配,在編譯時編譯器需要驗證一次、在類校驗階段的數據流分析中還要再次驗證

c.動態連接(Dynamic Linking):每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程中的動態連接

靜態解析和動態連接:Class文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用作爲參數,這些符號引用:

  • 一部分會在類加載階段或者第一次使用的時候就轉化爲直接引用(靜態解析
  • 另一部分會在每一次運行期間轉化爲直接引用(動態連接

d.方法返回地址

  • 方法退出的兩種方式:
    • 正常完成出口(Normal Method Invocation Completion):執行中遇到任意一個方法返回的字節碼指令;可能會給上層調用者傳遞返回值
    • 異常完成出口(Abrupt Method Invocation Completion):執行中遇到異常、且在本方法的異常表中沒有搜索到匹配的異常處理器區處理;不會給上層調用者傳遞返回值
  • 作用:無論哪種退出方式,在方法返回時都可能在棧幀中保存一些信息,用於恢復上層方法調用者的執行狀態
    • 正常退出時,調用者的PC計數器的值可以作爲返回地址,棧幀中很可能會保存這個計數器值
    • 異常退出時,通過異常處理器表來確定返回地址,棧幀中一般不會保存這部分信息
  • 方法退出的執行操作:恢復上層方法的局部變量表和操作數棧、若有返回值把它壓入調用者棧幀的操作數棧中、調整PC計數器的值以指向方法調用指令後面的一條指令等

e.附加信息:增加一些規範裏沒有描述的信息到棧幀之中,如與調試相關的信息

在實際開發中,一般會把動態連接、方法返回地址與其他附加信息全部一起稱爲棧幀信息


3.方法調用

  • 方法調用是最普遍、最頻繁的操作(方法調用≠方法執行)
  • 任務:確定被調用方法的版本,即調用哪一個方法,不涉及方法內部的具體運行過程
  • 類型:
    • 解析(Resolution)調用
      • 特點:是靜態過程;在編譯期間就完全確定,在類裝載的解析階段就會把涉及的符號引用全部轉變爲可確定的直接引用,而不會延遲到運行期再去完成,即編譯期可知、運行期不可變
      • 適用對象:靜態方法(與類型直接關聯)、私有方法(在外部不可被訪問),它們都不可能通過繼承或其他方式重寫其他版本
    • 分派(Dispatch)調用
      • 特點:可靜可動
      • 類型:
        • 靜態分派:依賴靜態類型來定位方法的執行版本;典型應用是方法重載;發生在編譯階段,不由Java虛擬機來執行
        • 動態分派:依賴動態類型來定位方法的執行版本;典型應用是方法重寫;發生在運行階段,由Java虛擬機來執行
        • 單分派:根據一個宗量對目標方法進行選擇(方法的接受者與方法的參數統稱爲方法的宗量)
        • 多分派:根據多於一個宗量對目標方法進行選擇

如何理解Java語言是一門靜態多分派、動態單分派的語言?


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