JVM_字節碼:(棧幀與操作數棧)及(符號引用與直接引用)

  • 棧幀(stack frame):
    棧幀是一種用于幫助虛擬機執行方法調用與方法執行的數據結構,歸屬于特定的一個線程,不存在并發的問題,本身是一種數據結構,實際上封裝了方法的局部變量表、動態鏈接信息、方法的返回地址及操作數棧等信息。Java中方法的調用都存在棧幀的操作,存在入棧和出棧的操作。對于棧操作的簡單理解:
    3-2=1
    3、2分別做入棧的操作,-出現分別出棧并計算出結果1 做入棧操作。

局部變量表:

  1. 用于存儲局部變量,都是用(slot)來描述局部變量的最小單位,32位int類型的數據,都會占用一個slot 對于long類型用2個連續的slot表示。
  2. slot是可以復用的,對于10個局部變量可能存在的slot<10 方法體可以存在更小的作用域,方法體內部的局部變量的作用域是不同的,在局部變量表不作區分,當b、c占據的slot結束了生命周期后,可能會被d、e占據,如:
public void test(){

         int a=3;

         if(a>4){
            int b=4;
            int c=5;
       }
      int d=7;
      int e=10;
}

動態鏈接是與C或者C++是不同的,對于C++來說Class之間的關系在編譯期間就已經確定好了,包括地址的偏移量會提前設置好,所以存在動態鏈接庫DLL Java是不同的,在編譯期間Class之間方法的調用在加載或者在真正開始調用的時候才能確定,基于上述的方面存在符號和直接引用。

  • 符號引用與直接引用
    符號引用:對于目標類的,比如對于一個類的全局類名的描述,存放在常量池中的。
    直接引用:是符合引用的內存地址,有時候在加載(或者第一次使用)的時候符號引用轉換過來,有時候在【每次】在運行期間進行轉換。分別稱為靜態解析(綁定)及動態連接。這種動態體現為Java的多態性。
Animal a=new Cat();
a.sleep();//invokevirtual
a=new Dog();

Java方法調用的字節碼指令:(5種)

1、invokeinterface:調用接口中的方法,實際上是在運行期決定的,決定調用實現該接口的哪一個對象的特定方法。(需要定位實現類)
2、invokestatic: 調用靜態方法。
3、invokespecial: 可以自己的私有方法(注意私有方法是不可以被重寫),也可以是構造方法(<init>),也可以調用父類的方法(成員或者構造器)
4、invokevirtual: 調用虛方法(C++中是存在的),是和多態緊密相關的 也是運行期動態查找的過程 查找繼承這個類或接口的方法。
5、invokedynamic: 動態調用方法。(1.7引入的)可以調用動態語言比如javascript。不是討論的重點。


Java源文件: 用于觀察static方法的調用

public class MyTest4 {
  public static void test(){
    System.out.println("test invoked");
  }
  public static void main(String[] args) {
    test();
  }
}

反編譯結果:

C:\spring_lecture\target\classes\com\compass\spring_lecture\binarycode>javap -v MyTest4
警告: 二進制文件MyTest4包含com.compass.spring_lecture.binarycode.MyTest4
Classfile /C:/spring_lecture/target/classes/com/compass/spring_lecture/binarycode/MyTest4.class
  Last modified 2019-7-4; size 664 bytes
  MD5 checksum 123e058e19e1836c1250aa6b13b35182
  Compiled from "MyTest4.java"
public class com.compass.spring_lecture.binarycode.MyTest4
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #25            // test invoked
   #4 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Methodref          #6.#28         // com/compass/spring_lecture/binarycode/MyTest4.test:()V
   #6 = Class              #29            // com/compass/spring_lecture/binarycode/MyTest4
   #7 = Class              #30            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/compass/spring_lecture/binarycode/MyTest4;
  #15 = Utf8               test
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               SourceFile
  #21 = Utf8               MyTest4.java
  #22 = NameAndType        #8:#9          // "<init>":()V
  #23 = Class              #31            // java/lang/System
  #24 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
  #25 = Utf8               test invoked
  #26 = Class              #34            // java/io/PrintStream
  #27 = NameAndType        #35:#36        // println:(Ljava/lang/String;)V
  #28 = NameAndType        #15:#9         // test:()V
  #29 = Utf8               com/compass/spring_lecture/binarycode/MyTest4
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/System
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Utf8               java/io/PrintStream
  #35 = Utf8               println
  #36 = Utf8               (Ljava/lang/String;)V
{
  public com.compass.spring_lecture.binarycode.MyTest4();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/compass/spring_lecture/binarycode/MyTest4;

  public static void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String test invoked
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1

         0: invokestatic  #5                  // Method test:()V 通過使用invokestatic調用

         3: return
      LineNumberTable:
        line 15: 0
        line 17: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  args   [Ljava/lang/String;
}

方法調用的解析過程:invokestatic invokespecial是在解析的過程中就可以確定的,靜態及自身父類的構造及成員方法。
靜態解析的4種情形:

  1. 靜態方法
  2. 父類方法
  3. 構造方法
  4. 私有方法(公有方法會被重寫或者復寫 因此存在多態)
    以上的4種方法就稱為非虛方法,它們是在類加載階段就可以將符號引用轉換為直接引用。

方法重載(overload): 方法的參數類型或者參數的個數不同,簽名的修飾符不算。

public class MyTest5 {

  public void test(Grandpa grandpa){
    System.out.println("grandpa");
  }

  public void test(Father father){
    System.out.println("father");
  }

  public void test(Son son){
    System.out.println("son");
  }

  public static void main(String[] args) {

    Grandpa p1=new Father();
    Grandpa p2=new Son();

    MyTest5 myTest5=new MyTest5();
    myTest5.test(p1);//grandpa
    myTest5.test(p2);//grandpa
  }
}
class Grandpa {
}

class Father extends Grandpa {
}

class Son extends Father {
}

上面的例子涉及到方法的靜態分派:
g1聲明的類型是Grandpa 是靜態類型 g1的真正指向的類型是Father(實際類型)。
我們可以得到這樣的一個結論:
變量的靜態類型是不會發生改變的,而變量的實際類型是可以發生變化的,是多態的一種體現,實際類型是在運行期間才可以確定的。
方法的重載是一種純粹靜態的一種行為,對JVM來說,是根據聲明的參數,而不是根據實際類型來決定的,是根據靜態類型來進行匹配的。是在編譯期間就可以完全確定的。
看一下的字節碼:



重載和重寫:重載和重寫是不同的,重載是靜態的,重寫是動態的。體現在靜態類型上面。


重寫的代碼示例:

public class MyTest6 {

//  apple
//  orange
//  orange
  public static void main(String[] args) {

    Furit apple = new Apple();
    Furit orange = new Orange();

    apple.test();
    orange.test();

    apple = new Orange();
    apple.test();

  }
}


class Furit {

  public void test() {

    System.out.println("fruit");

  }

}


class Apple extends Furit {

  @Override
  public void test() {

    System.out.println("apple");

  }
}

class Orange extends Furit {

  @Override
  public void test() {
    System.out.println("orange");

  }
}

編譯生成的字節碼:

public class com.compass.spring_lecture.binarycode.MyTest6 {
  public com.compass.spring_lecture.binarycode.MyTest6();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/compass/spring_lecture/binarycode/Apple
       3: dup
       4: invokespecial #3                  // Method com/compass/spring_lecture/binarycode/Apple."<init>":()V
       7: astore_1
       8: new           #4                  // class com/compass/spring_lecture/binarycode/Orange
      11: dup
      12: invokespecial #5                  // Method com/compass/spring_lecture/binarycode/Orange."<init>":()V
      15: astore_2
      16: aload_1
      17: invokevirtual #6                  // Method com/compass/spring_lecture/binarycode/Furit.test:()V
      20: aload_2
      21: invokevirtual #6                  // Method com/compass/spring_lecture/binarycode/Furit.test:()V
      24: new           #4                  // class com/compass/spring_lecture/binarycode/Orange
      27: dup
      28: invokespecial #5                  // Method com/compass/spring_lecture/binarycode/Orange."<init>":()V
      31: astore_1
      32: aload_1
      33: invokevirtual #6                  // Method com/compass/spring_lecture/binarycode/Furit.test:()V
      36: return
}

方法的動態分派:
從上述的字節碼看,方法的字節碼是一致的,但是從實際的調用上看,它們是不同的。
方法的動態分派涉及到一個重要的概念:方法的接收者,也就是方法的調用者

涉及到invokevirtual字節碼指令的多態查找流程:

  1. 到操作數棧頂上第一個元素并尋找棧頂元素所指向的實際類型,并不是靜態類型。
  2. 如果獲取到并且訪問權限也是ok的,就直接調用并返回,如果獲取不到按照繼承體系進行查找,找到就調用。

比較方法重寫和方法重載,我們得到這樣一個結論,方法重載是靜態的,是編譯期行為,方法重寫是動態的,是運行期行為。

動態分派的一個最直接的例子是重寫。對于重寫,我們已經很熟悉了,那么Java虛擬機是如何在程序運行期間確定方法的執行版本的呢?

解釋這個現象,就不得不涉及Java虛擬機的invokevirtual指令了,這個指令的解析過程有助于我們更深刻理解重寫的本質。該指令的具體解析過程如下:

  1. 找到操作數棧棧頂的第一個元素所指向的對象的實際類型,記為C

2 . 如果在類型C中找到與常量中描述符和簡單名稱都相符的方法,則進行訪問權限的校驗,如果通過則返回這個方法的直接引用,查找結束;如果不通過,則返回非法訪問異常

  1. 如果在類型C中沒有找到,則按照繼承關系從下到上依次對C的各個父類進行第2步的搜索和驗證過程

  2. 如果始終沒有找到合適的方法,則拋出抽象方法錯誤的異常

從這個過程可以發現,在第一步的時候就在運行期確定接收對象(執行方法的所有者程稱為接受者)的實際類型,所以當調用invokevirtual指令就會把運行時常量池中符號引用解析為不同的直接引用,這就是方法重寫的本質。


虛方法表和動態分派機制:
針對于方法調用動態分派的過程:
虛擬機會在類的方法區建立一個虛方法表的數據結構(virtual method table)簡稱vtable,
針對于invokeinterface指令來說,虛擬機會建立一個接口方法表的數據結構,(interface method table),簡稱itable。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容