虛擬機字節(jié)碼執(zhí)行引擎(讀書筆記)

在前面兩篇文章中介紹了 .class 文件的結構和虛擬機加載 .class 文件的過程绿淋,在本篇文章中主要介紹加載進來之后,虛擬機是如何執(zhí)行字節(jié)碼的翻具,在程序執(zhí)行的過程中主要是方法的調用和執(zhí)行弦撩,所以本篇文章中介紹虛擬機是如何調用方法并且執(zhí)行方法的瞒瘸,文章結構如下:


catalog.png

一. 概述

執(zhí)行引擎是 Java 虛擬機最核心的組成部分之一≈逞荩“虛擬機” 是一個相對于 “物理機” 的概念氧秘,這兩種機器都有代碼執(zhí)行能力,其區(qū)別是物理機的執(zhí)行引擎是直接建立在處理器趴久、硬件丸相、指令集和操作系統層面上的,而虛擬機的執(zhí)行引擎則是由自己實現的彼棍,因此可以自行制定指令集與執(zhí)行引擎的結構體系灭忠,并且能夠執(zhí)行哪些不被硬件直接支持的指令集格式。

在 Java 虛擬機規(guī)范中制定了虛擬機字節(jié)碼執(zhí)行引擎的概念模型座硕,這個概念模型稱為各種虛擬機執(zhí)行引擎的統一外觀(Facade)弛作。在不同的虛擬機實現里面,執(zhí)行引擎在執(zhí)行 Java 代碼的時候可能會有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時編譯器產生本地代碼執(zhí)行)兩種選擇华匾,也可能兩者兼?zhèn)溆沉眨踔吝€可能會包含幾個不同級別的編譯器執(zhí)行引擎。但從外觀上看起來蜘拉,所有的 Java 虛擬機的執(zhí)行引擎都是一致的:輸入的是字節(jié)碼文件萨西,處理過程是字節(jié)碼解析的等效過程,輸出的是執(zhí)行結果旭旭。

二. 運行時棧幀

棧幀(Stack Frame)是用于支持虛擬機進行方法調用和方法執(zhí)行的數據結構谎脯,它是虛擬機運行時數據區(qū)中的虛擬機棧(Virtual Machine Stack)的棧元素。棧幀存儲了方法的局部變量表您机、操作數棧穿肄、動態(tài)連接和方法返回地址等信息。每一個方法從調用開始至執(zhí)行完成的過程际看,都對應著一個棧幀在虛擬機棧里面從入棧到出棧的過程咸产。

每一個棧幀都包括了局部變量表、操作數棧仲闽、動態(tài)連接脑溢、方法返回地址和一些額外的附加信息。在編譯程序代碼的時候,棧幀中需要多大的局部變量表屑彻,多深的操作數棧都已經完全確定了验庙,并且寫入到方法表的 Code 屬性之中,因此一個棧幀需要分配多少內存社牲,不會受到程序運行期變量數據的影響粪薛,而僅僅取決于具體的虛擬機實現。

一個線程中的方法調用鏈可能會很長搏恤,很多方法都同時處于執(zhí)行狀態(tài)违寿。對于執(zhí)行引擎來說,在活動線程中熟空,只有位于棧頂的棧幀才是有效的藤巢,稱為當前棧幀(Current Stack Frame),與這個棧幀相關聯的方法稱為當前方法(Current Method)息罗。執(zhí)行引擎運行的所有字節(jié)碼指令都只針對當前棧幀進行操作掂咒,在概念模型上,典型的棧幀結構如下圖所示

stack_frame.png

接下來詳細講解一下棧幀的局部變量表迈喉、操作數棧绍刮、動態(tài)連接、方法返回地址等各個部分的作用和數據結構

2.1 局部變量表

局部變量表是一組變量值的存儲空間弊添,用于存放方法參數和方法內部定義的局部變量录淡。

在編譯的時候,就在方法的 Code 屬性的 max_locals 數據項中確定了該方法所需要分配的局部變量表的最大容量油坝。

局部變量表的容量以變量槽(Variable Slot嫉戚,下稱 Slot)為最小單位,虛擬機規(guī)范中并沒有明確指明一個 Slot 應占用的內存空間大小澈圈,只是很有導向性地說到每個 Slot 都應該能存儲一個 boolean彬檀、byte、char瞬女、short窍帝、int、float诽偷、reference 或 returnAddress 類型的數據坤学,這 8 中數據類型,都可以使用 32 位或更小的物理內存來存放报慕,在 Java 虛擬機的數據類型中深浮,64 位的數據類型只有 long 和 double 兩種,關于這幾種局部變量表中的數據有兩點需要注意

  • reference 數據類型眠冈,虛擬機規(guī)范并沒有明確指明它的長度飞苇,也沒有明確指明它的數據結構,但是虛擬機通過 reference 數據可以做到兩點:1. 通過此 reference 引用,可以直接或間接的查找到對象在 Java 堆上的其實地址索引布卡;2. 通過此 reference 引用雨让,可以直接或間接地查找到對象所屬數據類型在方法區(qū)中的存儲的類型信息
  • 對于 64 位的 long 和 double 數據,虛擬機會以高位對齊的方式為其分配兩個連續(xù)的 Slot 空間

在方法執(zhí)行時忿等,虛擬機是使用局部變量表完成參數變量列表的傳遞過程栖忠,如果是實例方法,那么局部變量表中的每 0 位索引的 Slot 默認是用于傳遞方法所屬對象實例的引用贸街,在方法中可以通過關鍵字 “this” 來訪問這個隱藏的局部變量娃闲,其余參數則按照參數列表的順序來排列,占用從 1 開始的局部變量 Slot匾浪,參數表分配完畢后,再跟進方法體內部定義的變量順序和作用域來分配其余的 Slot卷哩。需要注意的是局部變量并不存在如類變量的"準備"階段蛋辈,類變量會在類加載的時候經過“準備”和“初始化”階段,即使程序員沒有為類變量在 "初始化" 賦予初始值将谊,也還是會在"準備"階段賦予系統的類型默認值冷溶,但是局部變量不會這樣,局部變量表沒有"準備"階段尊浓,所以需要程序員手動的為局部變量賦予初始值

2.2 操作數棧

操作數棧也常被稱為操作棧逞频,它是一個后入先出棧。同局部變量表一樣栋齿,操作數棧的最大深度也是在編譯時期就寫入到方法表的 Code 屬性的 max_stacks 數據項中苗胀。操作數棧的每一個元素可以是可以是任意 Java 數據類型,包括 long 和 double瓦堵,32 位數據類型所占的棧容量為 1基协,64 位數據類型所占的棧容量為 2

在一個方法剛開始執(zhí)行的時候,操作數棧是空的菇用,隨著方法的執(zhí)行澜驮,會有各種字節(jié)碼往操作數棧中寫入和提取內容,也就是出棧/入棧操作惋鸥。

Java 虛擬機的解釋執(zhí)行引擎稱為"基于棧的執(zhí)行引擎"杂穷,其中所指的"棧"就是操作數棧。

棧容量的單位是 “字寬”卦绣,對于 32 位虛擬機來說耐量,一個 “字寬” 占 4 個字節(jié),對于 64 位虛擬機來說迎卤,一個 “字寬” 占 8 個字節(jié)

2.3 動態(tài)連接

每個棧幀都包含一個指向運行時常量池中該棧幀所屬性方法的引用拴鸵,持有這個引用是為了支持方法調用過程中的動態(tài)連接。

在 Class 文件的常量池中存有大量的符號引用,字節(jié)碼中的方法調用指令就以常量池中指向方法的符號引用為參數劲藐。這些符號引用一部分會在類加載階段或第一次使用的時候轉化為直接引用八堡,這種轉化稱為靜態(tài)解析。另外一部分將在每一次的運行期期間轉化為直接引用聘芜,這部分稱為動態(tài)連接兄渺。

2.4 方法返回地址

在一個方法被執(zhí)行后,有兩種方式退出這個方法:正常完成出口和異常完成出口

  • 正常完成出口:當執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令汰现,這時候可能會有返回值傳遞給上層的方法調用者(調用當前方法的方法稱為調用者)挂谍,是否有返回值和返回值的類型將根據遇到何種方法返回指令來決定
  • 異常完成出口:在方法執(zhí)行的過程中如果遇到了異常,并且這個異常沒有再方法體內得到處理瞎饲,無論是 Java 虛擬機內部產生的異常口叙,還是在代碼中使用 athrow 字節(jié)碼指令產生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器嗅战,就會導致方法退出

方法退出時妄田,需要返回到方法被調用的位置,程序才能繼續(xù)執(zhí)行驮捍。方法正常退出時疟呐,調用者的 PC 計數器的值可以作為返回地址,棧幀中很可能會保存這個計數器值东且;而方法異常退出時启具,返回地址是要通過異常處理器表來確定的,棧幀中一般不會保存這部分信息珊泳。

方法退出的過程實際上等同于把當前棧幀出棧鲁冯,因此退出時可能執(zhí)行的操作有:恢復上層方法的局部變量表和操作數棧,把返回值(如果有的話)壓入調用者棧幀的操作數棧中旨椒,調整 PC 計數器的值以指向方法調用指令后面的一條指令等

三. 方法調用

方法調用即指確認調用哪個方法的過程晓褪,并不是指執(zhí)行方法的過程。Java 的編譯并不包含傳統編譯過程中的連接步驟综慎,所以在 .java 代碼編譯成 .class 文件之后涣仿,在 .class 文件中存儲的是方法的符號引用(方法在常量池中的符號),并不是方法的直接引用(方法在內存布局中的入口地址)示惊,所以需要在加載或運行階段才會確認目標方法的直接引用好港。

3.1 解析

有幾種方法的調用,在加載階段就可以確認該方法的直接引用米罚,前提是:方法在程序真正運行之前就有一個可確定的調用版本(調用哪一個方法)钧汹,并且這個方法的調用版本在運行期是不可變的。換句話說录择,調用目標在程序代碼寫好拔莱、編譯器進行編譯時就必須確定下來碗降。這類方法的調用稱為解析。

有四種方法是進行的方法的解析:靜態(tài)方法塘秦、私有方法讼渊、實例構造器、父類方法尊剔,這四類方法稱為非虛方法爪幻,與之對應的就是續(xù)方法(final 方法除外),調用這四類方法的字節(jié)碼指令是:invokestatic须误、invokespecial 指令挨稿,也就是說被 invokestatic、invokespecial 字節(jié)碼調用的方法京痢,在類加載的解析階段就可以通過方法的符號引用確認方法的直接引用奶甘。
在 Java 字節(jié)碼中,還有幾種調用方法的字節(jié)碼指令如下:

  • invokestatic:調用靜態(tài)方法
  • invokespecial:調用實例構造器方法<init>祭椰、私有方法甩十、父類方法
  • invokevirtual:調用所有的虛方法
  • invokeinterface:調用接口方法,會在運行時確認一個實現此接口的對象
  • invokedynamic:先在運行時動態(tài)解析出調用點限定符所引用的方法吭产,然后再執(zhí)行該方法。
    被 final 關鍵字修飾的方法鸭轮,在字節(jié)碼中是被 invokevirtual 指令調用的臣淤,但是被 final 修飾的方法無法被重載或重寫,所以只有一個方法窃爷,在加載階段就可以確認調用哪個方法邑蒋,所以也是一種虛方法,方法調用時走的也是解析流程按厘。

3.2 分派

解析調用是一個靜態(tài)的過程医吊,在加載階段就可以確認目標方法的直接引用。分派調用有可能是靜態(tài)的逮京,也有可能是動態(tài)的卿堂,根據分派的宗量數又可以分為單分派和多分派,這兩類兩兩組合懒棉,所以分派共可以細分為:靜態(tài)單分派草描、靜態(tài)多分派、動態(tài)單分派策严、動態(tài)多分派穗慕。
在講解本節(jié)中的分派的過程中,會揭示一些 Java 中的多態(tài)性在 Java 虛擬機層面的基本體現妻导,如“重載”和“重寫”在 Java 虛擬機中是如何實現的逛绵。

3.2.1 靜態(tài)分派

先看如下一個靜態(tài)分派的代碼示例:

public class StaticDispatch {

    static abstract class Human {

    }

    static class Man extends Human {

    }

    static class Woman extends Human {

    }

    public void sayHello(Human guy) {
        System.out.println("hello, guy");
    }

    public void sayHello(Main guy) {
        System.out.println("hello, man");
    }

    public void sayHello(Woman guy) {
        System.out.println("hello, woman");
    }

    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch dispatch = new StaticDispatch();
        dispatch.sayHello(man);
        dispatch.sayHello(woman);
    }
}

有 Java 開發(fā)經驗的開發(fā)者都會知道怀各,上面代碼是一個方法重載的示例代碼,其輸出結果如下所示:

hello, guy
hello, guy

有人就問了术浪,為什么會調用參數類型是 Human 的方法瓢对,而不執(zhí)行方法參數是 Man 和 Woman 的方法呢?接下來我們就來分析一下添吗,在分析之前沥曹,我們先定義兩個重要的概念:變量的靜態(tài)類型和實際類型,假如有如下代碼:

Human man = new Man();
  • 靜態(tài)類型:是指對象 man 的 Human 類型碟联, 靜態(tài)類型本身是不會發(fā)送變化的妓美,只有在使用時才會發(fā)送變化,靜態(tài)類型在編譯期間就可以確定一個變量的靜態(tài)類型
  • 實際類型:是指對象 man 的 Man 類型鲤孵,實際類型在編譯期間是不可確定的壶栋,只有在運行期才可確定
    如下代碼所示:
// 實際類型變化
Human man = new Man();
man = new Woman();

// 靜態(tài)類型變化
dispatch.sayHello((Man) man);
dispatch.sayHello((Woman) man);

所以第一段代碼中,方法接收者是 StaticDispatch 對象普监,雖然兩個變量的實際類型不同贵试,但是靜態(tài)類型是相同的都是 Human,虛擬機(準確的說是編譯器)在實現重載時是通過參數的靜態(tài)類型而不是實際類型做出判定的凯正,并且在編譯階段毙玻,變量的靜態(tài)類型是可以確定的,所以編譯器會根據變量的靜態(tài)類型決定使用哪個重載方法廊散。
所有依賴靜態(tài)類型定位目標方法的分派動作稱為靜態(tài)分派桑滩,靜態(tài)分派典型的應用就是方法的重載。靜態(tài)分派發(fā)生在編譯階段允睹,所以方法的靜態(tài)分派動作是由編譯器執(zhí)行的运准。

3.2.2 動態(tài)分派

動態(tài)分派和 Java 語言中的"方法重寫"有著密切的聯系,還是看如下的一個例子:

public class DynamicDispatch {

    static abstract class Human {
        abstract void sayHello();
    }

    static class Man extends Human {
        void sayHello() {
            System.out.println("hello, man");
        }
    }

    static class Woman extends Human {
        void sayHello() {
            System.out.println("hello, woman");
        }
    }

    public static void main(String[] args){
        Human man = new Man();
        Human woman = new Woman();
        man.sayHello();
        woman.sayHello();
        man = new Woman();
        man.sayHello();
    }
}

輸出結果如下所示:

hello, man
hello, woman
hello, man

上面的輸出結果不會出乎人的預料缭受,從之前的靜態(tài)分派中看锉,我們可以指定黔牵,在這個例子中药版,不是根據對象的靜態(tài)類型判斷的畜晰,而是根據對象的實際類型判斷的,那在 Java 虛擬機中是如何根據實例類型來判斷的呢蔓搞?我們使用 javap 命令得到上面 main() 方法的字節(jié)碼如下所示:


DynamicDispatch.png

從上圖中陆盘,我們可以看到 main() 方法的字節(jié)碼指令執(zhí)行過程:

  • 0 ~ 7 句是調用 Man 類的實例構造器創(chuàng)建一個 Man 類的對象,并將對象的引用壓入到局部變量表的第 1 個 Slot 中
  • 8 ~ 15 句是調用 Woman 類的實例構造器創(chuàng)建一個 Woman 類的對象败明,并將對象的引用壓入到局部變量表的第 2 個 Slot 中
  • 16 ~ 17 句是將第 1 個 Slot 中的變量(也就是 man)加載到局部變量表中隘马,并調用 sayHello() 方法,關鍵的就是第 17 句指令 invokevirtual

雖然第 17 句指令調用的常量池中的 Human.sayHello() 方法妻顶,但是最終執(zhí)行的卻是 Man.sayHello() 方法酸员,這就要從 invokevirtual 指令的多態(tài)查找說起蜒车,invokevirtual 的查找過程如下所示:

  • 找到操作數棧頂的引用所指的對象的實際類型,記做 C
  • 在類型 C 中查找與常量中的描述符和簡單名稱相同的方法幔嗦,如果找到則進行訪問權限的判斷酿愧,如果通過則返回這個方法的直接引用,查找結束邀泉;如果權限不通過嬉挡,則返回 java.lang.IllegalAccessError 的異常
  • 如果在 C 中沒有找到描述符和簡單名稱都符合的方法,則按照繼承關系從下往上依次在 C 的父類中進行查找和驗證過程
  • 如果最終還是沒有找到該方法汇恤,則拋出 java.lang.AbstractMethodError 的異常

在上述 invokespecial 查找方法的過程中庞钢,最重要的就是第一步,根據對象的引用確定對象的實際類型因谎,這個方法重寫的本質基括。如上所述,在運行期內财岔,根據對象的實際類型確定方法執(zhí)行版本的分派過程叫做動態(tài)分派风皿。

3.2.3 單分派和多分派

分派根據基于多少種總量,可以分為單分派和多分派匠璧⊥┛睿總量是指:方法的接收者和方法的參數。根據分派時依據的宗量多少夷恍,可以分為單分派和多分派鲁僚。

到目前為止,Java 語言還是一門 "靜態(tài)多分派裁厅、動態(tài)單分派" 的語言,也就是說在執(zhí)行靜態(tài)分派時是根據多個宗量判斷調用哪個方法的侨艾,因為在靜態(tài)分派時要根據不同的靜態(tài)類型和不同的方法描述符選擇目標方法执虹,在動態(tài)分派的時候,是根據單宗量選擇目標方法的唠梨,因為在運行期袋励,方法的描述符已經確定好,invokevirtual 字節(jié)碼指令根據變量的實際類型選擇目標方法当叭。

3.2.4 虛擬機動態(tài)分派的實現

虛擬機中的動態(tài)分派是十分頻繁的動作茬故,并且是在運行時在類方法元數據中進行搜索的,因此基于性能的考慮蚁鳖,虛擬機會采用各種優(yōu)化手段優(yōu)化動態(tài)分派的過程磺芭,最常見的"穩(wěn)定優(yōu)化"的手段就是為類在方法區(qū)中建立一個虛方法表,使用虛方法表索引來代替元數據以提高性能醉箕。


virtual_table.png

上圖就是一個虛方法表钾腺,Father徙垫、Son、Object 三個類在方法區(qū)中都有一個自己的虛方法表放棒,如果子類中實現了父類的方法姻报,那么在子類的虛方法表中該方法就指向子類實現的該方法的入口地址,如果子類中沒有重寫父類中的方法间螟,那么在子類的虛方法表中吴旋,該方法的索引就指向父類的虛方法表中的方法的入口地址。有兩點需要注意:

  • 為了程序實現上的方便厢破,一個具有相同簽名的方法荣瑟,在子類的方法表和父類的方法表中應該具有相同的索引,這樣在類型變化的時候溉奕,只需要改變查找方法的虛方法表即可褂傀。
  • 虛方法表是在類加載的連接階段實現的,類的變量初始化完成之后加勤,就會初始化該類的虛方法表

四. 基于棧的字節(jié)碼解釋執(zhí)行引擎

本節(jié)我們探討虛擬機是如何執(zhí)行方法中的字節(jié)碼指令的仙辟。Java 虛擬機的執(zhí)行引擎在執(zhí)行 Java 代碼的時候都有解釋執(zhí)行和編譯執(zhí)行兩種選擇,我們探討一下解釋執(zhí)行時鳄梅,虛擬機執(zhí)行引擎是如何工作的叠国。

4.1 解釋執(zhí)行 & 編譯執(zhí)行 & 編譯器

在開始之前,先介紹一下解釋執(zhí)行和編譯執(zhí)行的含義

  1. 解釋執(zhí)行:代碼由生成字節(jié)碼指令之后戴尸,由解釋器解釋執(zhí)行
  2. 編譯執(zhí)行:通過即時編譯器生成本地代碼執(zhí)行

如下圖所示粟焊,中間這條分支是解釋執(zhí)行,下面那條分支是編譯執(zhí)行


image.png

Java 程序在執(zhí)行前先對程序源碼進行詞法分析和語法分析處理孙蒙,把源代碼轉化抽象語法樹项棠。對于一門具體語言的實現來說,詞法分析挎峦、語法分析以及后面的優(yōu)化器和目標代碼生成器都可以選擇獨立于執(zhí)行引擎香追,形成一個完整意義的編譯器去實現,這類代表是 C/C++ 語言坦胶。當然也可以選擇其中的一部分步驟實現一個半獨立的編譯器透典,這類代表是 Java 語言,又或者把這些步驟和執(zhí)行引擎全部集中封裝到一個封閉黑匣子中顿苇,如大多數的 JS 執(zhí)行器峭咒。

Java 語言中,Javac 編譯器完成了詞法分析纪岁、語法分析凑队、轉換為抽象語法樹,然后再生成字節(jié)碼指令流的過程幔翰,這些動作是獨立于 Java 虛擬機之外的顽决,所以 Javac 編譯器是一個半獨立的編譯器短条。

4.2 基于棧的指令集和基于寄存器的指令集

基于棧的指令集中的指令是依賴于操作數棧運行的,基于寄存器的指令是依賴于寄存器進行工作的才菠。那么它們兩者有什么區(qū)別呢茸时?用一個簡單的例子說明:1 + 1 這個例子來說明

  • 基于棧的指令如下:

      iconst_1
      iconst_1
      iadd
      istore_0
    

    兩條 iconst_1 指令分別把兩個 1 壓入到工作棧中去,然后執(zhí)行 iadd 兩條指令赋访,對棧頂的兩個 1 進行出棧并相加的動作可都,然后將相加的結果 2 壓入到棧中,接著執(zhí)行 istore_0 將棧頂的 2 存入到局部變量表中第 0 個 Slot 中去

  • 基于寄存器的指令如下:

      mov  eax,1
      add   eax, 1
    

    mov 指令將寄存器 eax 中的值設置為 1蚓耽,然后執(zhí)行 add 指令將寄存器 eax 中的值加 1渠牲,結果就保存在 eax 寄存器中

基于棧的指令的特點

  • 可移植:寄存器由硬件決定,限制較大步悠,但是虛擬機可以在不同硬件條件的機器上執(zhí)行
  • 代碼相對更加緊湊:字節(jié)碼中每個字節(jié)就對應一條指令签杈,而多地址指令集中還需要存放參數
  • 編譯器實現更加簡單
  • 基于棧的指令缺點就是執(zhí)行速度慢,因為虛擬機中操作數棧是在內存中實現的鼎兽,頻繁的棧訪問也就意味著頻繁的訪問內存答姥,內存的訪問還是要比直接操作寄存器要慢的

4.3 基于棧的解釋器執(zhí)行過程

我們通過一段示例代碼來學習基于棧的解釋器執(zhí)行過程,示例代碼如下所示:

public class Test {
    public int calc() {
        int a = 100;
        int b = 200;
        int c = 300;
        return (a + b) * c;
    }
}

執(zhí)行 javac Test.java 生成 Test.class 字節(jié)碼文件之后谚咬,再使用 javap -verbose Test.class 命令查看 Test.class 字節(jié)碼指令如下圖所示:

Test.png

由上圖中可以看到鹦付,Test#calc() 方法對應的字節(jié)碼如下:

  public int calc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        100
         2: istore_1
         3: sipush        200
         6: istore_2
         7: sipush        300
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: imul
        16: ireturn
      LineNumberTable:
        line 3: 0
        line 4: 3
        line 5: 7
        line 6: 11

從上面的字節(jié)碼指令中可以分析到:calc() 方法需要深度為 2 的操作數棧和 4 個 Slot 的局部變量空間,如下 7 張圖描述上述代碼執(zhí)行過程中的代碼择卦、操作數棧和局部變量表的變化情況


image.png

class_jvm2.png

class_jvm3.png

上面的執(zhí)行過程僅僅是一種概念模型敲长,虛擬機最終會對執(zhí)行過程做一些優(yōu)化來提高性能,實際的運作過程不一定完全符合概念模型的描述秉继。

更準確的說祈噪,實際情況會和上面描述的概念模型差距非常大,這種差距產生的原因是虛擬機中解釋器和即時編譯器都會對輸入的字節(jié)碼進行優(yōu)化尚辑。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末辑鲤,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子腌巾,更是在濱河造成了極大的恐慌,老刑警劉巖铲觉,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澈蝙,死亡現場離奇詭異,居然都是意外死亡撵幽,警方通過查閱死者的電腦和手機灯荧,發(fā)現死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盐杂,“玉大人逗载,你說我怎么就攤上這事哆窿。” “怎么了厉斟?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵挚躯,是天一觀的道長。 經常有香客問我擦秽,道長码荔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任感挥,我火速辦了婚禮缩搅,結果婚禮上,老公的妹妹穿的比我還像新娘触幼。我一直安慰自己硼瓣,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布置谦。 她就那樣靜靜地躺著堂鲤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪霉祸。 梳的紋絲不亂的頭發(fā)上筑累,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音丝蹭,去河邊找鬼慢宗。 笑死,一個胖子當著我的面吹牛奔穿,可吹牛的內容都是我干的镜沽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼贱田,長吁一口氣:“原來是場噩夢啊……” “哼缅茉!你這毒婦竟也來了?” 一聲冷哼從身側響起男摧,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蔬墩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耗拓,有當地人在樹林里發(fā)現了一具尸體拇颅,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年乔询,在試婚紗的時候發(fā)現自己被綠了樟插。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黄锤,靈堂內的尸體忽然破棺而出搪缨,到底是詐尸還是另有隱情,我是刑警寧澤鸵熟,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布副编,位于F島的核電站,受9級特大地震影響旅赢,放射性物質發(fā)生泄漏齿桃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一煮盼、第九天 我趴在偏房一處隱蔽的房頂上張望短纵。 院中可真熱鬧,春花似錦僵控、人聲如沸香到。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悠就。三九已至,卻和暖如春充易,著一層夾襖步出監(jiān)牢的瞬間梗脾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工盹靴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炸茧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓稿静,卻偏偏與公主長得像梭冠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子改备,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容