java符號(hào)的定位與解析(總結(jié)版)

java符號(hào)的定位與解析,從本質(zhì)上來看,就是一個(gè)消除歧義的過程躲惰,這個(gè)過程從編譯到運(yùn)行励七,橫跨了多個(gè)階段智袭,本文主要目的在于劃分清楚哪個(gè)階段消除了什么歧義

1.符號(hào)的起點(diǎn)-java代碼

public class Parent {
  public void sayHi(){
  }
 public static void main(String[] args) {
    new Parent().sayHi("hi");
  }
}

那么從java語義上看,表達(dá)的意思大概就是:
1掠抬、有個(gè)public的Parent類
2吼野、這個(gè)類有個(gè)類方法叫sayHi
3、類方法sayHi是個(gè)沒有參數(shù)也沒有返回值的public方法
4两波、main函數(shù)調(diào)用了sayHi函數(shù)
如同你畫我猜一般瞳步,如何將這些意思精確的傳遞給jvm直到最終程序的運(yùn)行呢

2.編譯后-字節(jié)碼

編譯的過程就是將某一種語言編寫的程序翻譯成為一個(gè)等價(jià)的、用另一種語言編寫的程序雨女。那么對于java而言谚攒,編譯就是將java代碼翻譯成字節(jié)碼阳准,編譯的細(xì)節(jié)就不分析了氛堕,主要看編譯的結(jié)果--字節(jié)碼,以下是截取的部分字節(jié)碼野蝇,分為兩塊讼稚,一塊是方法表,一塊是常量池

常量池中有一個(gè)常量指向sayHi函數(shù):

Constant pool:
   #..."省略"
   #4 = Methodref          #2.#17         // Parent.sayHi:()V

Parent.sayHi:()V就是用來描述sayHi函數(shù)的符號(hào)引用绕沈,描述的很精確:類Parent的锐想,沒有入?yún)⒌模祷豽oid的乍狐,名為sayHi的方法
那么main方法里怎么調(diào)用這個(gè)sayHi()的呢赠摇,看方法表:

public static void main(java.lang.String[]);
 ...
    7: invokevirtual #4                  // Method sayHi:()V
 ...

invokevirtual可以暫時(shí)理解成調(diào)用,#4當(dāng)然就是上文中常量池中的常量#4浅蚪,它指向sayHi()方法藕帜,如此一個(gè)調(diào)用函數(shù)的過程就被字節(jié)碼描述清楚了

從這個(gè)簡單的例子可以得出結(jié)論:
字節(jié)碼中的符號(hào)引用僅僅是一個(gè)描述性的符號(hào)(如:Parent.sayHi:()V),并沒有保存最終的內(nèi)存布局信息惜傲,這點(diǎn)與C語言的編譯結(jié)果不一樣

3.編譯期間解決的歧義

3.1 重載

假如一個(gè)類中洽故,void sayHi(String string)void sayHi(Object object)兩個(gè)方法構(gòu)成重載,當(dāng)調(diào)用 sayHi("hi")時(shí)盗誊,看看字節(jié)碼中的指令是什么:

invokevirtual #9                  // Method sayHi:(Ljava/lang/String;)V

顯然时甚,#9常量指向的是void sayHi(String string)方法,那么可以得出結(jié)論:
編譯期間哈踱,重載造成的歧義就被解決了荒适,在字節(jié)碼中,已經(jīng)選出應(yīng)該調(diào)用的方法

繼續(xù)看這個(gè)例子开镣,調(diào)用方法時(shí)傳入的參數(shù)為"hi",既是String類型刀诬,也是Object類型,但是在字節(jié)碼中哑子,直接認(rèn)定了調(diào)用(Ljava/lang/String;)V舅列,這是為什么呢肌割,以下是《深入理解jvm》書中給出的解釋:

Java 編譯器選取重載方法的過程共分為三個(gè)階段:
1、在不考慮對基本類型自動(dòng)裝拆箱(auto-boxing帐要,auto-unboxing)把敞,以及可變長參數(shù)的情況下選取重載方法;
2榨惠、如果在第 1 個(gè)階段中沒有找到適配的方法奋早,那么在允許自動(dòng)裝拆箱,但不允許可變長參數(shù)的情況下選取重載方法赠橙;
3耽装、如果在第 2 個(gè)階段中沒有找到適配的方法,那么在允許自動(dòng)裝拆箱以及可變長參數(shù)的情況下選取重載方法期揪。
如果 Java 編譯器在同一個(gè)階段中找到了多個(gè)適配的方法掉奄,那么它會(huì)在其中選擇一個(gè)最為貼切的,而決定貼切程度的一個(gè)關(guān)鍵就是形式參數(shù)類型的繼承關(guān)系凤薛。

3.2 遮蔽

下面這段代碼姓建,在init()函數(shù)中定義了一個(gè)與類變量同名的局部變量a

public class Parent5 {
  public String a = "out";
  public void init(){
       String a = "in";
       System.out.println(a);
       System.out.println(this.a);
  }
  public static void main(String[] args){
       new Parent5().init();
  }
}

直接看字節(jié)碼中是如何獲取a和this.a對應(yīng)的值的:
常量池

Constant pool:
   #3 = Fieldref           #7.#24         // Parent5.a:Ljava/lang/String;
   #4 = String             #25            // in

獲取a的值

0: ldc           #4                  // String in

從常量池的#4常量中獲取值(指向了字符串"in"

獲取this.a的值

getfield      #3                  // Field a:Ljava/lang/String;

從常量池的#3常量中獲取值(指向了Parent5.a:Ljava/lang/String,即類變量a)
顯然,字節(jié)碼層面上已經(jīng)對調(diào)用哪個(gè)變量區(qū)分的明明白白缤苫,那么可以得出結(jié)論:
java語言層面上定義的遮蔽速兔,其在編譯過程中就被實(shí)現(xiàn)

3.3 隱藏

隱藏的典型場景是對于一個(gè)靜態(tài)方法,多態(tài)的特性失效了
java代碼

public class Parent4 {
  public static void sayHi(){
    System.out.println("hi,son");
  }
}

class Son4 extends Parent4{
  public static void sayHi(){
      System.out.println("hi,parent");
   }

  public static void main(String[] args) {
    Parent4 son4 = new Son4();
    son4.sayHi();
  }
}

本例中活玲,將打印出"hi,son"涣狗,這與多態(tài)的場景基本一致,區(qū)別在于本例中的方法是靜態(tài)方法舒憾,所以盡管真正的實(shí)例是Son4類型的镀钓,但最后還是調(diào)用的父類的方法

son4.sayHi();對應(yīng)的字節(jié)碼

10: invokestatic  #7                  // Method Parent4.sayHi:()V

上述指令中,#7常量指向的是父類方法(Method Parent4.sayHi:()V)珍剑,且使用的是invokestatic指令掸宛,這條指令是專門用來調(diào)用靜態(tài)方法的,具體invokestatic的實(shí)現(xiàn)邏輯可以看源碼解析
簡單總結(jié)一下就是: 在解析invokestatic #7時(shí)招拙,直接將#7指向的符號(hào)引用解析成直接引用并返回唧瘾,并不關(guān)心運(yùn)行時(shí)實(shí)際對象的類型(即只有連接時(shí)解析,沒有運(yùn)行時(shí)解析别凤,后文會(huì)進(jìn)行介紹)

那么可以得出結(jié)論:
java語言層面上定義的隱藏饰序,是在編譯時(shí)消除的歧義

4.解析期間解決的歧義

上文講到,在字節(jié)碼中规哪,調(diào)用方法時(shí)求豫,指向的只是一個(gè)方法的符號(hào)引用,而不是最終的內(nèi)存地址,那么就需要一個(gè)步驟將符號(hào)引用轉(zhuǎn)為直接引用蝠嘉,這一步驟稱為解析最疆,以下是一些基本的名詞解釋

符號(hào)引用:是以一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量蚤告,只要使用時(shí)能無歧義地定位到目標(biāo)即可. 符號(hào)引用的目標(biāo)不一定要加載到內(nèi)存中.

直接引用:是直接指向目標(biāo)的指針努酸、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄. 如果有了直接引用,那引用的目標(biāo)必定存在于內(nèi)存中.

虛擬機(jī)規(guī)范中并沒有明確規(guī)定解析階段發(fā)生的具體時(shí)間杜恰,只要求了在執(zhí)行 anewarray获诈、checkcast、getfield心褐、getstatic舔涎、instanceof、invokedynamic逗爹、invokeinterface亡嫌、invokespecial、invokestatic桶至、invokevirtual昼伴、ldc匾旭、ldc_w镣屹、multianewarray、new价涝、putfield女蜈、putstatic 用于操作符號(hào)引用的字節(jié)碼指令前,先對它們所使用的符號(hào)進(jìn)行解析. 所以虛擬機(jī)實(shí)現(xiàn)可以根據(jù)需要來判斷到底是在類被加載時(shí)就對常量池中的符號(hào)進(jìn)行解析色瘩,還是等到一個(gè)符號(hào)將要被使用前才解析它.

4.1 多態(tài)&重寫

以一個(gè)多態(tài)的例子來分析解析的過程
java代碼

class Son3 extends Parent3
...
Parent3 son3 = new Son3();
son3.sayHi();

son3.sayHi()對應(yīng)的字節(jié)碼

invokevirtual #7                  // Method Parent3.sayHi:()V

可以看出伪窖,#7常量指向的是父類方法(Method Parent3.sayHi:()V),顯然在編譯后居兆,歧義沒有消除覆山,那么來看解析invokevirtual指令,可以將其分為兩個(gè)步驟:連接時(shí)解解析運(yùn)行時(shí)解析 (根據(jù)源碼函數(shù)翻譯而來)

4.1.1 連接時(shí)解析(linktime_resolve_virtual_method)

根據(jù)字節(jié)碼中的符號(hào)引用Parent3.sayHi:()V泥栖,解析其對應(yīng)的直接引用簇宽,具體尋找過程如下(源碼可看另一篇文章):
1、遍歷Parent3類的方法列表吧享,根據(jù)符號(hào)引用來找到匹配的方法魏割,并返回它的直接引用(methodHandle)
2、如果第一步找不到钢颂,就去Parent3的父類中找钞它,還找不到,就去Parent3實(shí)現(xiàn)的接口中找

4.1.2 運(yùn)行時(shí)解析(runtime_resolve_virtual_method)

連接時(shí)解析已經(jīng)獲得了Parent3.sayHi:()V對應(yīng)的直接引用methodHandle,接下來的步驟是:
1遭垛、根據(jù)methodHandle尼桶,獲取sayHi()函數(shù)在Parent3類對應(yīng)虛方法表中的位置vtable_index
2、jvm在運(yùn)行時(shí)可以獲取到實(shí)際對象的類型锯仪,即Son3類疯汁,獲取其對應(yīng)的虛表
3、從Son3的虛方法表中獲取vtable_index位置指向的直接引用

解析符號(hào)引用.png
invokevirtual #7                  // Method Parent3.sayHi:()V

解析前卵酪,常量#7指向Parent3.sayHi:()V的符號(hào)引用
解析后幌蚊,常量#7指向Son3::sayHi()V的直接引用
這就是多態(tài)的實(shí)現(xiàn),在運(yùn)行時(shí)解析消除了歧義
解析的過程中設(shè)計(jì)到了虛方法表的概念溃卡,下面會(huì)進(jìn)行介紹

4.1.3 虛方法表

虛方法表(簡稱虛表)的具體初始化細(xì)節(jié)和源碼可以看另一篇文章溢豆,這里總結(jié)一些相關(guān)的概念:
1、虛表是一種用于實(shí)現(xiàn)dynamic dispatch機(jī)制(或者說runtime method binding機(jī)制瘸羡,也就是我們說的多態(tài))的工具漩仙,C++也有用到
2、每個(gè)類對應(yīng)一個(gè)虛表犹赖,虛表中不存放靜態(tài)方法和私有方法(只為invoke_virtual服務(wù))
3队他、虛表中,也會(huì)存放父類的方法峻村,且對于同一個(gè)函數(shù)麸折,在父類和子類的虛表中的位置是一樣的,如果沒有重寫,二者指向同一個(gè)引用粘昨,若發(fā)生重寫垢啼,則不同

舉個(gè)簡單的例子,Son3重寫了Parent3的sayHi方法张肾,以下是它們對應(yīng)的虛表


虛方法表.png

5. 總結(jié)

1芭析、編譯期間消除了重載、遮蔽吞瞪、隱藏這三種情況的歧義
2馁启、解析是符號(hào)引用轉(zhuǎn)化成直接引用的過程
3、解析可分為連接時(shí)解析和運(yùn)行時(shí)解析兩步芍秆,靜態(tài)方法的解析沒有第二步
4惯疙、多態(tài)&重寫則是在解析時(shí)(通過虛表)消除的歧義

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浪听,隨后出現(xiàn)的幾起案子螟碎,更是在濱河造成了極大的恐慌,老刑警劉巖迹栓,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掉分,死亡現(xiàn)場離奇詭異俭缓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)酥郭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門华坦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人不从,你說我怎么就攤上這事惜姐。” “怎么了椿息?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵歹袁,是天一觀的道長。 經(jīng)常有香客問我寝优,道長条舔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任乏矾,我火速辦了婚禮孟抗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钻心。我一直安慰自己凄硼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布捷沸。 她就那樣靜靜地躺著摊沉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亿胸。 梳的紋絲不亂的頭發(fā)上坯钦,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音侈玄,去河邊找鬼。 笑死吟温,一個(gè)胖子當(dāng)著我的面吹牛序仙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲁豪,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼潘悼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了爬橡?” 一聲冷哼從身側(cè)響起治唤,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糙申,沒想到半個(gè)月后宾添,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年缕陕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粱锐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扛邑,死狀恐怖怜浅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蔬崩,我是刑警寧澤恶座,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站沥阳,受9級特大地震影響奥裸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沪袭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一湾宙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冈绊,春花似錦侠鳄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至毅该,卻和暖如春博秫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背眶掌。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工挡育, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朴爬。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓即寒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親召噩。 傳聞我的和親對象是個(gè)殘疾皇子母赵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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