一文搞懂 JVM 字節碼執行引擎

2021-01-07 計算機java編程

學習導圖

一.為什麼要學習字節碼執行引擎?

代碼編譯的結果從本地機器碼轉變為字節碼,是存儲格式發展的一小步,卻是程式語言發展的一大步

首先,拋出靈魂三問:

虛擬機在執行代碼的時候,如何找到正確的方法呢?如何執行方法內的字節碼呢?執行代碼時涉及的內存結構有哪些呢?如果你對上述問題理解得還不是特別透徹的話,可以看下這篇文章;如果理解了,你可以關閉網頁,打開遊戲放鬆了hhh

下面,筆者將帶你探究 JVM 核心的組成部分之一——執行引擎。

二.核心知識點歸納

2.1 概述

Q1:虛擬機與物理機的異同

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

Q2:有關 JVM字節碼執行引擎的概念模型

外觀上:所有 JVM 的執行引擎都是一致的。輸入的是字節碼文件,處理的是字節碼解析的等效過程,輸出的是執行結果

從實現上,執行引擎有多種執行 Java 代碼的選擇1.解釋執行:通過解釋器執行2.編譯執行:通過即時編譯器產生本地代碼執行3.兩者兼備,甚至還會包含幾個不同級別的編譯器執行引擎

2.2 運行時棧幀結構

2.2.1 基本概念

筆者之前在 一文洞悉 JVM 內存管理機制 中就談到過虛擬機棧,相信看過的讀者都有印象

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

2.2.2 局部變量表

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

訪問方式:通過索引定位。索引值的範圍是從 0 開始至局部變量表最大的變量槽數量局部變量表第一項是名為 this 的一個當前類引用,它指向堆中當前對象的引用(由反編譯得到的局部變量表可知)

2.2.3 操作數棧

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

定義:每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接靜態解析和動態連接區別:Class 文件的常量池中存有大量的符號引用,字節碼中的方法調用指令就以常量池中指向方法的符號引用作為參數,這些符號引用:1. 一部分會在類加載階段或者第一次使用的時候就轉化為直接引用(靜態解析)2. 另一部分會在每一次運行期間轉化為直接引用(動態連接)

2.2.5 方法返回地址

方法退出的兩種方式:

1. 正常退出:執行中遇到任意一個方法返回的字節碼指令2. 異常退出:執行中遇到異常、且在本方法的異常表中沒有搜索到匹配的異常處理器區處理

作用:在方法返回時都可能在棧幀中保存一些信息,用於恢復上層方法調用者的執行狀態1. 正常退出時,調用者的 PC 計數器的值可以作為返回地址2. 異常退出時,通過異常處理器表來確定返回地址

方法退出的執行操作:1. 恢復上層方法的局部變量表和操作數棧2. 若有返回值把它壓入調用者棧幀的操作數棧中3. 調整 PC 計數器的值以指向方法調用指令後面的一條指令等

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

2.3 方法調用

方法調用是最普遍且頻繁的操作任務:確定被調用方法的版本,即調用哪一個方法,不涉及方法內部的具體運行過程下面筆者將為大家詳細講解方法調用的類型

2.3.1 解析調用

筆者之前在 一夜搞懂 | JVM 類加載機制中就談到過解析,感覺有點混淆的,可以回去看下

特點:是靜態過程在編譯期間就完全確定,在類裝載的解析階段就會把涉及的符號引用全部轉變為可確定的直接引用,而不會延遲到運行期再去完成,即編譯期可知、運行期不變適用對象:private 修飾的私有方法,類靜態方法,類實例構造器,父類方法2.3.2 分派調用

Q1:什麼是靜態類型?什麼是實際類型?

A1:這個用代碼來說比較簡便, Talk is cheap ! Show me the code !

1.靜態分派

1. 依賴靜態類型來定位方法的執行版本2. 典型應用是方法重載3. 發生在編譯階段,不由 JVM 來執行單純說未免有些許抽象,所以特地用下面的 DEMO 來幫助了解

輸出結果如下:

hello , i am the fatherhello , i am the father

我們的編譯器在生成字節碼指令的時候會根據變量的靜態類型選擇調用合適的方法。就我們上述的例子而言:

2.動態分派

1. 依賴動態類型來定位方法的執行版本2. 典型應用是方法重寫3. 發生在運行階段,由 JVM 來執行單純說未免有些許抽象,所以特地用下面的 DEMO 來幫助了解

輸出結果如下:

hello world ---- son

我們接著來看一下字節碼指令調用情況

疑惑來了,我們可以看到,JVM 選擇調用的是靜態類型的對應方法,但是為什麼最終的結果卻調用了是實際類型的對應方法呢?

當我們將要調用某個類型實例的具體方法時,會首先將當前實例壓入操作數棧,然後我們的 invokevirtual 指令需要完成以下幾個步驟才能實現對一個方法的調用:

因此,疑惑自然解決了

3.單分派

含義:根據一個宗量對目標方法進行選擇(方法的接受者與方法的參數統稱為方法的宗量)

4.多分派

含義:根據多於一個宗量對目標方法進行選擇

想了解 靜態多分派,動態單分派的可以看下這篇文章:Java 中的靜態單多分派與動態單分派

三.碎碎念

恭喜你!已經看完了前面的文章,相信你對JVM字節碼執行引擎已經有一定深度的了解!你可以稍微放鬆獎勵自己一下,可以睡一個美美的覺,明天起來繼續衝衝衝!

如果文章對您有一點幫助的話,希望您能點一下贊,您的點讚,是我前進的動力

相關焦點

  • 這一次,徹底弄懂 Java 字節碼文件!
    執行如下命令:javap-verbosecom.dskj.jvm.bytecode.MyTest1生成字節碼文件內容:Classfile /...前面都是鋪墊,來到重磅分析的一節。這些方法也是以信息的形式存儲在編譯之後的字節碼class文件當中,接下來,JVM去執行字節碼文件時,當你調用某個特定方法時,JVM才能根據你所編寫的原始碼的意圖去執行字節碼裡的指令。對於這個方法來說,在JVM中最終是形成一條條指令的去執行的,也就是說在字節碼裡形成的每一條指令對應源碼文件中的每一行原始碼。
  • Java虛擬機系列一:一文搞懂 JVM 架構和運行時數據區
    1.1 Class 文件 (字節碼文件)Java 之所以號稱「一次編寫,處處運行」,就是得益於虛擬機和 Class 文件 (註:CLass 文件、字節碼文件和類文件是一個意思) 的組合機制。1.4 執行引擎 (Execution Engine)字節碼被加載進運行時數據區後,執行引擎會進行讀取並執行,執行引擎主要包含以下模塊:解釋器 (Interpreter):相信大家很久以前就聽過「計算機只認識0和1」這句話,時至今日,計算機依然只認識0和1,所以任何程式語言的代碼最終都要轉化成機器碼 (二進位代碼)才能執行,Java 也不例外,而解釋器的工作正是將編譯得到的字節碼再轉化成機器碼
  • JavaScript引擎實現JVM 支持運行Java
    而使用JVM是實現這一特點的關鍵。一般的高級語言如果要在不同的平臺上運行,至少需要編譯成不同的目標代碼。而引入JVM後,Java語言在不同平臺上運行時不需要重新編譯。Java語言使用JVM屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。JVM在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。
  • 想理解JVM看了這篇文章,就知道了!
    執行引擎 執行引擎用於執行JVM字節碼指令,主要有兩種方式,分別是解釋執行和編譯執行,區別在於,解釋執行是在執行時翻譯成虛擬機指令執行,而編譯執行是在執行之前先進行編譯再執行。解釋執行啟動快,執行效率低。編譯執行,啟動慢,執行效率高。垃圾回收器就是自動管理運行數據區的內存,將無用的內存佔用進行清除,釋放內存資源。
  • 一文讓你明白 Java 字節碼
    Java號稱是一門「一次編譯到處運行」的語言,但是我們對這句話的理解深度又有多少呢?從我們寫的java文件到通過編譯器編譯成java字節碼文件(也就是.class文件),這個過程是java編譯過程;而我們的java虛擬機執行的就是字節碼文件。不論該字節碼文件來自何方,由哪種編譯器編譯,甚至是手寫字節碼文件,只要符合java虛擬機的規範,那麼它就能夠執行該字節碼文件。
  • Python程序執行過程與字節碼
    Python 執行程序分為兩步:先將程序代碼編譯成字節碼,然後啟動虛擬機執行字節碼:雖然 python 命令也叫做 Python 解釋器 ( Interpreter ),但跟其他腳本語言解釋器有本質區別。
  • JDK、JRE、JVM,是什麼關係?
    jvm.dll可能你之前並沒有注意過 jvm 原來在這裡:C:\Program Files\Java\jdk1.8.0_45\jre\bin\server這部分是整個 Java 實現跨平臺的最核心內容,由 Java 程序編譯成的 .class 文件會在虛擬機上執行。另外在 JVM 解釋 class 文件時需要調用類庫 lib。
  • JVM內存區域之線程私有區域
    操作數棧本質上是 JVM 執行引擎的一個工作區,也就是方法在執行,才會對操作數棧進行操作,如果代碼不不執行,操作數棧其實就是空的。動態連接:Java 語言特性多態(後續章節細講,需要結合 class 與執行引擎一起來講)。
  • JVM-概述和內存區域
    舉個例子將groovy編譯之後的class文件用jvm運行1、先配置好groovy環境方法區和堆區是所有線程共享的內存區域;Java棧又叫做jvm虛擬機棧。執行引擎等同於翻譯class文件的語言翻譯器。
  • 面經手冊 · 第23篇《JDK、JRE、JVM,是什麼關係?》
    JVM(Java Virtual Machine Java虛擬機),JVM可以理解為是一個虛擬出來的計算機,具備著計算機的基本運算方式,它主要負責把 Java 程序生成的字節碼文件,解釋成具體系統平臺上的機器指令,讓其在各個平臺運行。
  • 你想了解JDK、JRE、JVM分別是什麼及它們之間的有什麼關聯嗎?
    JREJRE:Java runtime environment 是運行基於Java語言編寫的程序所不可缺少的運行環境,用於解釋執行Java的字節碼文件。它是整個java實現跨平臺的最核心的部分,負責解釋執行字節碼文件,是可運行java字節碼文件的虛擬計算機。所有平臺的上的JVM向編譯器提供相同的接口,而編譯器只需要面向虛擬機,生成虛擬機能識別的代碼,然後由虛擬機來解釋執行。
  • 聊到JVM(還怕面試官問JVM嗎?)
    3、JVM體系結構 ‍類裝載器子系統運行時數據區執行引擎本地方法接口垃圾收集模塊            System.out.println(class1.hashCode());        System.out.println(class2.hashCode());        System.out.println(class3.hashCode());    }}    首先Class Loader讀取字節碼
  • JVM 面試基礎準備篇(一)
    JVM 面試基礎準備篇(一)1. 計算機原理2.2.1  Class文件2.1.1 生成字節碼文件package com.example.jvm.clazz;class Person {    private
  • JVM中的五大內存區域劃分詳解及快速掃盲
    簡單用一張圖來理解這三個的關係:3. jvm的組成成分不了解jvm的同學看到這張圖後可能會有點懵逼,不過沒關係,放這張圖只是想讓你了解jvm中有三塊內容非常重要,1.java代碼如何執行?運行java文件的大概流程想要運行java的源文件,必須要經過javac編譯器編譯成.class文件,也就是字節碼文件。然後通過jvm中的解釋器,解釋成特定機器上的機器碼。每種機器上的解釋器是不一樣的,我們經常用的也就是windows和linux系統,這也是為什麼java能夠跨平臺的原因。
  • JVM、GC 大串講,面試夠用了
    JVM JRE JDK的關係JVM(java虛擬機),將 .class 文件中的字節碼指令進行識別並調用作業系統向上的 API 完成動作。JVM不僅可以運行java程序,只要是能編譯成.class的文件都能運行。JRE (Java 運行時環境),包含了jvm和core lib。JDK (Java 開發工具包),它集成了jre和一些工具。
  • 20道阿里面試必問JVM面試專題(文末附答案及JVM學習文檔)
    小編再為大家推薦一本JVM設計原理和實現的PDF文檔:本書從源碼角度解讀HotSpot的內部實現機制,本版本主要包含三大部分JVM數據結構設計與實現、執行引擎機制及內存分配模型。數據結構部分包括Java字節碼文件格式、常量池解析、欄位解析、方法解析。
  • 程式設計師每日一題-jvm裡方法和方法區、棧區的二三事
    堆區是jvm裡面最需要深入研究的一塊區域,這裡面涉及內存分配,區域劃分,對象信息,垃圾回收。可以說如果java程式設計師對堆區不熟悉,那麼一定寫不出好的代碼。本文暫時不深入討論,後續會開專題深入講解。B:棧區,也叫虛擬機棧,顧名思義,它是一個棧,先進後出。它是線程創建時跟著創建,生命周期和線程一致,是線程私有的。
  • 這一定是全網寫JVM最好的文章之一—JVM運行時數據區
    一個Java程序,首先要經過javac編譯成.class文件,.class文件是給JVM進行識別的,JVM將.class文件加載到方法區,執行引擎會執行這些字節碼,執行時,會翻譯成作業系統相關的函數。為什麼說Java是一次編譯到處運行不管是windows,mac,還是linux,unix,oracle官網上都提供了下載對應的jdk版本,我們只需要編寫java代碼,因為jvm不同作業系統上下載的不同版本的,所以不需要我們管各種作業系統之間的區別,jvm只識別字節碼,所以jvm其實跟語言是解耦的,也沒有直接關聯。
  • 字節碼層面理解try、catch、finally
    面試中經常有關於try、catch、finally相關的問題,今天從字節碼層面了解他們的運行流程。在這裡我們通過jclasslib查看編譯後的字節碼,並找到方法的字節碼指令,如下圖:右邊被圈中的就是test()方法執行的字節碼指令,字節碼指令較長,接下來一部分一部分的分析。
  • 來自JVM的一封ClassFile介紹信
    我在jvm中佔有很重要的地位,你可去看看jvm規範中我佔了多少篇幅,告訴你,足足有大半本書!第四章,標題叫做 The class File Format。可以這麼說。你把編譯器、字節碼、jit那些看過以後,再把我搞清楚,基本上jvm你也就精通了。鑑於我這麼重要,今天我介紹下自己。