《深入理解java虛擬機(jī)》-虛擬機(jī)字節(jié)碼執(zhí)行引擎

在java虛擬機(jī)規(guī)范中定制了虛擬機(jī)字節(jié)碼執(zhí)行引擎的概念模型,這個(gè)概念模型成為各種虛擬機(jī)執(zhí)行引擎的統(tǒng)一外觀(Facade)。從外觀上看,所有java虛擬機(jī)的執(zhí)行引擎都是一致的:輸入字節(jié)碼文件诫睬,輸出執(zhí)行結(jié)果

運(yùn)行時(shí)幀棧結(jié)構(gòu)

棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素效五。典型棧幀結(jié)構(gòu):

棧幀的概念結(jié)構(gòu)

局部變量表

局部變量表(Local Variable Table)是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量炉峰。局部變量表的容量以變量槽(Variable Slot)為最小單位畏妖,虛擬機(jī)規(guī)范中并沒有明確指定一個(gè)Slot應(yīng)占用的內(nèi)存空間大小,只是規(guī)定每個(gè)Slot都應(yīng)該能存放一個(gè)boolean疼阔、byte戒劫、char、short婆廊、int迅细、float、reference或returnAddress類型的數(shù)據(jù)淘邻,這樣可以屏蔽32位跟64位虛擬機(jī)在內(nèi)存空間上的差異茵典。

虛擬機(jī)通過索引定位的方式使用局部變量表,索引值的范圍從0到最大Slot數(shù)量宾舅,索引n對(duì)應(yīng)第n個(gè)Slot统阿。局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,即this筹我。

為了盡可能的節(jié)省棧幀空間砂吞,局部變量表中的Slot是可以重用的,同時(shí)這也影響了垃圾收集行為崎溃。即對(duì)已使用完畢的變量,局部變量表仍持有該對(duì)象的引用盯质,導(dǎo)致對(duì)象無法被GC回收袁串,占用大量內(nèi)存。這也是“不使用的對(duì)象應(yīng)手動(dòng)賦值為null”這條推薦編碼規(guī)則的原因呼巷。不過從執(zhí)行角度使用賦null值的操作來優(yōu)化內(nèi)存回收是建立在對(duì)字節(jié)碼執(zhí)行引擎概念模型的理解之上囱修,代碼在經(jīng)過編譯器優(yōu)化后才是虛擬機(jī)真正需要執(zhí)行的代碼,這時(shí)賦null值會(huì)被消除掉王悍,因此更優(yōu)雅的解決辦法是以恰當(dāng)?shù)淖兞孔饔糜?/strong>來控制變量回收時(shí)間破镰。

操作數(shù)棧

操作數(shù)棧(Operand Stack)也常稱操作棧,它是一個(gè)后入先出(Last In First Out,LIFO)棧。方法在執(zhí)行過程中鲜漩,通過各種字節(jié)碼指令對(duì)棧進(jìn)行操作源譬,出棧/入棧。java虛擬機(jī)的解釋執(zhí)行引擎稱為“基于棧的執(zhí)行引擎”孕似,其中所指的“棽饶铮”就是操作數(shù)棧

動(dòng)態(tài)連接

每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用時(shí)為了執(zhí)行方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)

方法返回地址

當(dāng)一個(gè)方法開始執(zhí)行后喉祭,只有兩種方式可以退出這個(gè)方法:

  1. 執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令养渴,這個(gè)時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的方法稱為調(diào)用者),這種退出方式稱為正常完成出口(Normal Method Invocation Completion)
  2. 方法執(zhí)行過程中遇到了異常泛烙,并且這個(gè)異常沒有在方法體內(nèi)得到處理理卑,無論是java虛擬機(jī)內(nèi)部產(chǎn)生的異常,還是代碼使用athrow字節(jié)碼指令產(chǎn)生的異常蔽氨,只要在本方法的異常表中沒有搜索到匹配的異常處理器藐唠,就會(huì)導(dǎo)致方法退出,這種退出方式稱為異常完成出口(Abrupt Method Invocation Completion)孵滞,這時(shí)不會(huì)給它的上層調(diào)用者產(chǎn)生任何返回值

方法退出的過程實(shí)際上就等同于把當(dāng)前棧幀出棧中捆,因此退出時(shí)可能執(zhí)行的操作有:

  • 恢復(fù)上層方法的局部變量表和操作數(shù)棧
  • 把返回值(如果有)壓入調(diào)用者棧幀的操作數(shù)棧
  • 調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指定等

附加信息

虛擬機(jī)規(guī)范允許具體的虛擬機(jī)實(shí)現(xiàn)增加一些規(guī)范里沒有描述的信息到棧幀中,稱之為棧幀信息

方法調(diào)用

方法調(diào)用并不等同于方法執(zhí)行坊饶,方法調(diào)用階段的唯一任務(wù)就是確定被調(diào)用方法的版本泄伪,即調(diào)用哪一個(gè)方法,暫時(shí)還不涉及方法內(nèi)部的具體運(yùn)行過程匿级,就是類加載過程中的類方法解析蟋滴。

解析

解析就是將Class的常量池中的符號(hào)引用轉(zhuǎn)化為直接引用。在java虛擬機(jī)中提供了5條方法調(diào)用字節(jié)碼指令:

  • invokestatic:調(diào)用靜態(tài)方法

    System.exit(1);
    ==>編譯
    iconst_1    ;將1放入棧內(nèi)
                ;執(zhí)行System.exit()
    invokestatic java/lang/System/exit(I)V
    
  • invokespecial:調(diào)用實(shí)例構(gòu)造器<init>方法痘绎、私有方法和父類方法

    //<init>方法
    new StringBuffer()
    ==>編譯
    new java/lang/StringBuffer    ;創(chuàng)建一個(gè)StringBuffer對(duì)象
    dup                           ;將對(duì)象彈出棧頂
                                  ;執(zhí)行<init>()來初始化對(duì)象
    invokespecial java/lang/StringBuffer/<init>()V
    
    //父類方法
    super.equals(x);
    ==>編譯
    aload_0   ;將this入棧
    aload_1   ;將第一個(gè)參數(shù)入棧
              ;執(zhí)行Object的equals()方法
    invokespecial java/lang/Object/equals(Ljava/lang/Object;)Z
    
    //私有方法
    與父類方法類似
    
  • invokevirtual:調(diào)用所有的虛方法

    X x;
    ...
    x.equals("abc");
    ==>編譯
    aload_1   ;將x入棧
    ldc "abc"   ;將“abc”入棧
              ;執(zhí)行equals()方法
    invokevirtual X/equals(Ljava/lang/Object;)Z
    
  • invokeinterface:調(diào)用接口方法津函,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)此接口的對(duì)象

    List x;
    ...
    x.toString();
    ==>編譯
    aload_1   ;將x入棧
              ;執(zhí)行toString()方法
    invokeinterface java/util/List/toString()Z
    
  • invokedynamic:先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法

在編譯階段就可以確定唯一調(diào)用版本的方法有:靜態(tài)方法(類名)孤页、私有方法尔苦、實(shí)例構(gòu)造器(<init>父類方法(super)行施、final方法允坚。其它統(tǒng)稱為虛方法,在編譯階段無法確定調(diào)用版本蛾号,需要在運(yùn)行期通過分派將符號(hào)引用轉(zhuǎn)變?yōu)橹苯右?/p>

分派

  1. 靜態(tài)分派:是指在運(yùn)行時(shí)對(duì)類內(nèi)相同名稱的方法根據(jù)描述符來確定執(zhí)行版本的分派稠项,即方法重載
  2. 動(dòng)態(tài)分派:是指對(duì)于相同方法簽名的方法根據(jù)實(shí)際執(zhí)行對(duì)象來確定執(zhí)行版本的分派。編譯器是根據(jù)引用類型來判斷方法是否可執(zhí)行鲜结,真正執(zhí)行的是實(shí)際對(duì)象方法
  3. 單分派與多分派:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量展运。單分派是根據(jù)根據(jù)一個(gè)宗量對(duì)方法進(jìn)行選擇活逆,多分派是根據(jù)多個(gè)宗量對(duì)方法進(jìn)行選擇
  4. 虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn):由于動(dòng)態(tài)分派是非常頻繁的動(dòng)作,基于性能的考慮拗胜,虛擬機(jī)中最常用的“穩(wěn)定優(yōu)化”手段是為類在方法區(qū)中建立一個(gè)虛方法表(Virtual Method Table蔗候,與此對(duì)應(yīng)的,在invokeinterface執(zhí)行時(shí)也會(huì)用到接口方法表Interface Method Table)挤土,使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能琴庵。除使用方法表外,還可以使用內(nèi)聯(lián)緩存(Inline Cache)和基于“類型繼承關(guān)系分析”(Class Hierarchy Analysis,CHA)技術(shù)的守護(hù)內(nèi)聯(lián)(Guarded Inlining)等
虛方法表結(jié)構(gòu)

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

解釋執(zhí)行

編譯過程
  • 將上面步驟獨(dú)立于執(zhí)行引擎仰美,形成一個(gè)完整的編譯器 ==> C/C++
  • 將其中一部分步驟實(shí)現(xiàn)為一個(gè)半獨(dú)立的編譯器 ==> Java
  • 將上面步驟和執(zhí)行引擎全部集中封裝在一個(gè)封閉的黑匣子中 ==> JavaScript(大多數(shù)執(zhí)行器)

基于棧的指令集與基于寄存器的指令集

  • 基于棧的指令集:
    • 優(yōu)點(diǎn):可移植迷殿、代碼相對(duì)更緊湊、編譯器實(shí)現(xiàn)更簡單等
    • 缺點(diǎn):執(zhí)行速度慢咖杂、完成相同功能的指令數(shù)量更多庆寺、棧位于內(nèi)存中
  • 基于寄存器的指令集
    • 優(yōu)點(diǎn):速度快
    • 缺點(diǎn):與硬件結(jié)合緊密

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市诉字,隨后出現(xiàn)的幾起案子懦尝,更是在濱河造成了極大的恐慌,老刑警劉巖壤圃,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陵霉,死亡現(xiàn)場離奇詭異,居然都是意外死亡伍绳,警方通過查閱死者的電腦和手機(jī)踊挠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冲杀,“玉大人效床,你說我怎么就攤上這事∪ㄋ” “怎么了剩檀?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旺芽。 經(jīng)常有香客問我沪猴,道長,這世上最難降的妖魔是什么采章? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任运嗜,我火速辦了婚禮,結(jié)果婚禮上共缕,老公的妹妹穿的比我還像新娘。我一直安慰自己士复,他們只是感情好图谷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布翩活。 她就那樣靜靜地躺著,像睡著了一般便贵。 火紅的嫁衣襯著肌膚如雪菠镇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天承璃,我揣著相機(jī)與錄音利耍,去河邊找鬼。 笑死盔粹,一個(gè)胖子當(dāng)著我的面吹牛隘梨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舷嗡,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼轴猎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了进萄?” 一聲冷哼從身側(cè)響起捻脖,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎中鼠,沒想到半個(gè)月后可婶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡援雇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年矛渴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熊杨。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡曙旭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晶府,到底是詐尸還是另有隱情桂躏,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布川陆,位于F島的核電站剂习,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏较沪。R本人自食惡果不足惜鳞绕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尸曼。 院中可真熱鬧们何,春花似錦、人聲如沸控轿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹦蠕,卻和暖如春冒签,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钟病。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工萧恕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肠阱。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓票唆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辖所。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惰说,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容