BiBi - JVM -10- 虛擬機(jī)字節(jié)碼

From:深入理解Java虛擬機(jī)

物理機(jī):執(zhí)行引擎直接建立在處理器、硬件娃善、指令集和操作系統(tǒng)層面上踪央。
虛擬機(jī):執(zhí)行引擎由自己實(shí)現(xiàn),可以自行制定指令集曲饱。

1. 棧楨

棧楨是虛擬機(jī)方法調(diào)用過(guò)程的數(shù)據(jù)結(jié)構(gòu),主要包括:局部變量表、操作數(shù)棧一疯、動(dòng)態(tài)鏈接存炮、方法返回地址等炬搭。在編譯代碼的時(shí)候,棧楨中需要多大的局部變量表穆桂,多深的操作數(shù)棧都已經(jīng)全部確定了宫盔,并且寫入到方法表的Code屬性之中【其中max_loclas代表局部變量表的最大容量】,因此一個(gè)棧楨需要分配多少內(nèi)存享完,不會(huì)受到程序運(yùn)行期變量數(shù)據(jù)的影響灼芭。

  • 局部變量表

存放方法參數(shù)和方法內(nèi)部定義的局部變量。局部變量表的容量以【變量槽般又,Slot】為最小單位彼绷,一個(gè)Slot可以存放一個(gè)32位以內(nèi)的數(shù)據(jù)類型。對(duì)于long和double 64位的數(shù)據(jù)會(huì)以高位對(duì)齊的方式分配兩個(gè)連續(xù)的Slot空間茴迁。在方法執(zhí)行時(shí)寄悯,虛擬機(jī)使用局部變量表完成【參數(shù)值】到【參數(shù)變量列表】的傳遞。

Slot復(fù)用影響垃圾回收行為:

//方式一:變量b還在作用域之內(nèi)堕义,所以不會(huì)回收b的內(nèi)存猜旬。
public static void main(String[] args) {
  byte[] b = new byte[1024 * 1024 * 64];
  System.gc();
}
//方式二:由于Slot復(fù)用的原因脚曾,b不會(huì)被回收
public static void main(String[] args) {
  {
    byte[] b = new byte[1024 * 1024 * 64];
  }
  System.gc();
}
//方式三:b被回收
public static void main(String[] args) {
  {
    byte[] b = new byte[1024 * 1024 * 64];
  }
  int a = 0;
  System.gc();
}

上面三種方式中b能否被回收的根本原因:局部變量表中的Slot是否還存有b數(shù)組對(duì)象的引用双肤。方式二雖然已經(jīng)離開(kāi)了b的作用域,但在此之后沒(méi)有任何對(duì)局部變量表的讀寫操作猛频,b原本所占用的Slot還沒(méi)有被其它變量所復(fù)用糖耸,所以作為GC Roots一部分的局部變量表仍然保持著對(duì)他的關(guān)聯(lián)秘遏。【也可以通過(guò)手動(dòng)設(shè)置b = null,來(lái)達(dá)到回收的效果】但嘉竟,方式二邦危,經(jīng)過(guò)JIT編譯后,可以回收舍扰,所以無(wú)需像方式三那樣處理倦蚪。

注意:局部變量沒(méi)有默認(rèn)值。

  • 操作數(shù)棧

在編譯的時(shí)候?qū)⒆畲笊疃葘懭氲紺ode屬性的max_stacks數(shù)據(jù)項(xiàng)中边苹。兩個(gè)棧楨作為虛擬機(jī)元素陵且,是完全獨(dú)立的。但在大多虛擬機(jī)的實(shí)現(xiàn)里都會(huì)做一些優(yōu)化處理,令兩個(gè)棧楨一部分重疊慕购,這樣在方法調(diào)用時(shí)可以共用一部分?jǐn)?shù)據(jù)聊疲,無(wú)需進(jìn)行額外的參數(shù)復(fù)制傳遞。

Java虛擬機(jī)是基于棧的執(zhí)行引擎沪悲,其中所指的棧就是【操作數(shù)椈裰蓿】。

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

每個(gè)棧楨都包含一個(gè)指向運(yùn)行時(shí)【常量池】中該棧楨所屬方法的引用殿如,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接贡珊。Class文件的常量池中存有大量的【符號(hào)引用】,字節(jié)碼中的方法調(diào)用指令就是以常量池中指向方法的符號(hào)引用作為參數(shù)涉馁。這些符號(hào)引用一部分在類加載階段或第一次使用的時(shí)候就轉(zhuǎn)化為直接引用门岔,這種轉(zhuǎn)化稱為【靜態(tài)解析,如:靜態(tài)方法烤送、私有方法寒随、實(shí)例構(gòu)造器、父類方法胯努。即編譯期可知牢裳,運(yùn)行期間不可變】;另外一部分在每次運(yùn)行期間轉(zhuǎn)化為直接引用叶沛,這種轉(zhuǎn)換稱為【動(dòng)態(tài)連接】蒲讯。

2. 方法調(diào)用

invokestatic
invokespecial
invokevirtual
invointerface
invokedynamic【分派邏輯不是由虛擬機(jī)決定的,而是由程序員決定】

Class文件的編譯過(guò)程中不包含傳統(tǒng)編譯中的連接步驟灰署,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用判帮,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址。此特性可以在類加載期間溉箕,或在運(yùn)行期間再確定目標(biāo)方法的直接引用晦墙,使動(dòng)態(tài)擴(kuò)展能力更強(qiáng)。

靜態(tài)解析的字節(jié)碼指令:invokestatic 肴茄、 invokespecial【實(shí)例構(gòu)造器晌畅、私有方法、父類方法】和被final修飾的方法寡痰。在類加載的時(shí)候把符號(hào)引用解析為直接引用抗楔。

  • 靜態(tài)分派【針對(duì)重載】
public class StaticDispatch {
  static abstract class Human {
  }

  static class Man extends Human {
  }

  public void say(Human obj) {
    System.out.println("human");
  }

  public void say(Man obj) {
    System.out.println("man");
  }

  public static void main(String[] args) {
    Human man = new Man();
    StaticDispatch st = new StaticDispatch();
    st.say(man); //human
  }
}

輸出結(jié)果為:human。
Human man = new Man()中拦坠,Human為變量的【靜態(tài)類型】连躏;Man為變量的【實(shí)際類型】。靜態(tài)類型在編譯期可知贞滨,而實(shí)際類型在運(yùn)行期才可確定入热。重載是通過(guò)參數(shù)的靜態(tài)類型而不是實(shí)際類型作為判斷依據(jù)。所以,在編譯階段就決定了使用哪個(gè)重載版本勺良。

  • 動(dòng)態(tài)分派【針對(duì)多態(tài)的覆蓋】

運(yùn)行期間根據(jù)【實(shí)際類型】確定方法執(zhí)行版本的分派過(guò)程稱為動(dòng)態(tài)分配绰播。
在類方法區(qū)中建立一個(gè)【虛方法表】,使用虛方法表索引來(lái)確定各個(gè)方法的實(shí)際入口郑气。具有相同簽名的方法幅垮,在父類腰池、子類的虛方法表中都具有一樣的索引號(hào)尾组。

3. 動(dòng)態(tài)語(yǔ)言支持

JDK7字節(jié)碼指令集中添加【invokedynamic指令】來(lái)支持動(dòng)態(tài)類型語(yǔ)言,也是為JDK8中的Lambda表達(dá)式做準(zhǔn)備示弓。

動(dòng)態(tài)語(yǔ)言關(guān)鍵特征:類型檢查在運(yùn)行期而不是編譯期讳侨。
特點(diǎn):變量無(wú)類型而變量值才有類型。對(duì)Java虛擬機(jī)而言奏属,它可以同時(shí)支持靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言【Groovy跨跨、JRuby】。

由于invokestatic囱皿、invokespecial勇婴、invokevirtual、invointerface的第一個(gè)參數(shù)都是【被調(diào)用方法的符號(hào)引用】嘱腥,該符號(hào)引用在編譯時(shí)產(chǎn)生耕渴,而動(dòng)態(tài)語(yǔ)言只有在運(yùn)行期間才能確定接收者的類型,這種底層問(wèn)題只有在虛擬機(jī)層次上去解決才是最合適的齿兔,因此才有invokedynamic指令誕生橱脸。

java.lang.invoke包在之前單純依靠符號(hào)引用來(lái)確定調(diào)用的目標(biāo)方法之外,提供了一種新的動(dòng)態(tài)目標(biāo)方法機(jī)制分苇,稱為【MethodHandle添诉,類似于函數(shù)指針】。

MethodHandler效果與Reflection反射類似都是在模擬方法調(diào)用医寿,其區(qū)別如下:
1)Reflection在模擬Java代碼層次的方法調(diào)用栏赴,而MethodHandler在模擬字節(jié)碼層次的方法調(diào)用。
2)Reflection是重量級(jí)的靖秩,它包含方法的簽名须眷、描述符、方法屬性表等盆偿;而MethodHandler是輕量級(jí)的柒爸,只包含執(zhí)行該方法相關(guān)信息。
3)Reflection只是為Java語(yǔ)言設(shè)計(jì)的事扭,而MethodHandler可服務(wù)于所有Java虛擬機(jī)上的語(yǔ)言捎稚。

  • 問(wèn)題:通過(guò)super可以調(diào)用父類中的方法,如何調(diào)用祖父類中的方法呢?
package com.ljg;

import android.os.Build;
import android.support.annotation.RequiresApi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;

import static java.lang.invoke.MethodHandles.lookup;

public class Test {
  class GrandFather {
    void say() {
      System.out.println("grandFather");
    }
  }

  class Father extends GrandFather {
    void say() {
      System.out.println("father");
    }
  }

  class Son extends Father {
    void say() {
      //調(diào)用父類中的方法
      super.say();
      //調(diào)用祖父類中的方法
      try {
        MethodType methodType = MethodType.methodType(void.class);
        MethodHandle methodHandle =
            lookup().findSpecial(GrandFather.class, "say", methodType, getClass());
        methodHandle.invoke(this);
      } catch (Throwable throwable) {
        throwable.printStackTrace();
      }
    }
  }
}

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

主流的PC機(jī)的指令集架構(gòu)是依賴【寄存器】進(jìn)行工作的今野。

  • 1+1基于棧的指令集:
    iconst_1
    iconst_1
    iadd
    istore_0【把棧頂?shù)闹捣诺骄植孔兞勘淼牡?個(gè)Slot中】

  • 1+1基于寄存器的指令集:
    mov eax, 1
    add eax, 1【結(jié)果保存在EAX寄存器中】

  • 基于棧的指令集的【優(yōu)點(diǎn)】:
    1)可移植葡公。寄存器是由硬件直接提供的,而使用棧架構(gòu)的指令集条霜,用戶程序不會(huì)直接使用寄存器催什,虛擬機(jī)可自行將一些訪問(wèn)頻繁的數(shù)據(jù)【如:程序計(jì)數(shù)器、棧頂緩存】放到寄存器中以獲得更好的性能宰睡。
    2)代碼緊湊
    3)編譯器實(shí)現(xiàn)簡(jiǎn)單

  • 基于棧的指令集的【缺點(diǎn)】:
    執(zhí)行速度慢蒲凶。原因:
    1)指令數(shù)量一般比寄存器架構(gòu)多,因?yàn)槌鰲H霔2僮骶蜁?huì)產(chǎn)生很多指令拆内。
    2)棧實(shí)現(xiàn)在內(nèi)存中旋圆,頻繁的棧訪問(wèn)也就意味著頻繁的內(nèi)存訪問(wèn)。盡管虛擬機(jī)可以采用【棧頂緩存】的手段麸恍,把最常用的操作映射到寄存器中避免直接訪問(wèn)內(nèi)存灵巧,但這只是優(yōu)化措施而不是解決問(wèn)題的本質(zhì)。
    所以抹沪,由于指令數(shù)量和內(nèi)存訪問(wèn)的原因刻肄,導(dǎo)致棧架構(gòu)的指令集執(zhí)行速度慢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末融欧,一起剝皮案震驚了整個(gè)濱河市敏弃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹬癌,老刑警劉巖权她,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異逝薪,居然都是意外死亡隅要,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門董济,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)步清,“玉大人,你說(shuō)我怎么就攤上這事虏肾±。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵封豪,是天一觀的道長(zhǎng)谴轮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吹埠,這世上最難降的妖魔是什么第步? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任疮装,我火速辦了婚禮,結(jié)果婚禮上粘都,老公的妹妹穿的比我還像新娘廓推。我一直安慰自己,他們只是感情好翩隧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布樊展。 她就那樣靜靜地躺著,像睡著了一般堆生。 火紅的嫁衣襯著肌膚如雪专缠。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天顽频,我揣著相機(jī)與錄音藤肢,去河邊找鬼太闺。 笑死糯景,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的省骂。 我是一名探鬼主播蟀淮,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钞澳!你這毒婦竟也來(lái)了怠惶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤轧粟,失蹤者是張志新(化名)和其女友劉穎策治,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兰吟,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡通惫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了混蔼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片履腋。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惭嚣,靈堂內(nèi)的尸體忽然破棺而出遵湖,到底是詐尸還是另有隱情,我是刑警寧澤晚吞,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布延旧,位于F島的核電站,受9級(jí)特大地震影響槽地,放射性物質(zhì)發(fā)生泄漏迁沫。R本人自食惡果不足惜烹卒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弯洗。 院中可真熱鬧旅急,春花似錦、人聲如沸牡整。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)逃贝。三九已至谣辞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沐扳,已是汗流浹背泥从。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沪摄,地道東北人躯嫉。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像杨拐,于是被迫代替她去往敵國(guó)和親祈餐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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