JVM_字節(jié)碼:(棧幀與操作數(shù)棧)及(符號(hào)引用與直接引用)

  • 棧幀(stack frame):
    棧幀是一種用于幫助虛擬機(jī)執(zhí)行方法調(diào)用與方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),歸屬于特定的一個(gè)線程,不存在并發(fā)的問(wèn)題,本身是一種數(shù)據(jù)結(jié)構(gòu),實(shí)際上封裝了方法的局部變量表、動(dòng)態(tài)鏈接信息、方法的返回地址及操作數(shù)棧等信息。Java中方法的調(diào)用都存在棧幀的操作,存在入棧和出棧的操作。對(duì)于棧操作的簡(jiǎn)單理解:
    3-2=1
    3、2分別做入棧的操作,-出現(xiàn)分別出棧并計(jì)算出結(jié)果1 做入棧操作。

局部變量表:

  1. 用于存儲(chǔ)局部變量,都是用(slot)來(lái)描述局部變量的最小單位,32位int類型的數(shù)據(jù),都會(huì)占用一個(gè)slot 對(duì)于long類型用2個(gè)連續(xù)的slot表示。
  2. slot是可以復(fù)用的,對(duì)于10個(gè)局部變量可能存在的slot<10 方法體可以存在更小的作用域,方法體內(nèi)部的局部變量的作用域是不同的,在局部變量表不作區(qū)分,當(dāng)b、c占據(jù)的slot結(jié)束了生命周期后,可能會(huì)被d、e占據(jù),如:
public void test(){

         int a=3;

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

動(dòng)態(tài)鏈接是與C或者C++是不同的,對(duì)于C++來(lái)說(shuō)Class之間的關(guān)系在編譯期間就已經(jīng)確定好了,包括地址的偏移量會(huì)提前設(shè)置好,所以存在動(dòng)態(tài)鏈接庫(kù)DLL Java是不同的,在編譯期間Class之間方法的調(diào)用在加載或者在真正開(kāi)始調(diào)用的時(shí)候才能確定,基于上述的方面存在符號(hào)和直接引用。

  • 符號(hào)引用與直接引用
    符號(hào)引用:對(duì)于目標(biāo)類的,比如對(duì)于一個(gè)類的全局類名的描述,存放在常量池中的。
    直接引用:是符合引用的內(nèi)存地址,有時(shí)候在加載(或者第一次使用)的時(shí)候符號(hào)引用轉(zhuǎn)換過(guò)來(lái),有時(shí)候在【每次】在運(yùn)行期間進(jìn)行轉(zhuǎn)換。分別稱為靜態(tài)解析(綁定)及動(dòng)態(tài)連接。這種動(dòng)態(tài)體現(xiàn)為Java的多態(tài)性。
Animal a=new Cat();
a.sleep();//invokevirtual
a=new Dog();

Java方法調(diào)用的字節(jié)碼指令:(5種)

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


Java源文件: 用于觀察static方法的調(diào)用

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

反編譯結(jié)果:

C:\spring_lecture\target\classes\com\compass\spring_lecture\binarycode>javap -v MyTest4
警告: 二進(jìn)制文件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 通過(guò)使用invokestatic調(diào)用

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

方法調(diào)用的解析過(guò)程:invokestatic invokespecial是在解析的過(guò)程中就可以確定的,靜態(tài)及自身父類的構(gòu)造及成員方法。
靜態(tài)解析的4種情形:

  1. 靜態(tài)方法
  2. 父類方法
  3. 構(gòu)造方法
  4. 私有方法(公有方法會(huì)被重寫或者復(fù)寫 因此存在多態(tài))
    以上的4種方法就稱為非虛方法,它們是在類加載階段就可以將符號(hào)引用轉(zhuǎn)換為直接引用。

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

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 {
}

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



重載和重寫:重載和重寫是不同的,重載是靜態(tài)的,重寫是動(dòng)態(tài)的。體現(xiàn)在靜態(tài)類型上面。


重寫的代碼示例:

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");

  }
}

編譯生成的字節(jié)碼:

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
}

方法的動(dòng)態(tài)分派:
從上述的字節(jié)碼看,方法的字節(jié)碼是一致的,但是從實(shí)際的調(diào)用上看,它們是不同的。
方法的動(dòng)態(tài)分派涉及到一個(gè)重要的概念:方法的接收者,也就是方法的調(diào)用者

涉及到invokevirtual字節(jié)碼指令的多態(tài)查找流程:

  1. 到操作數(shù)棧頂上第一個(gè)元素并尋找棧頂元素所指向的實(shí)際類型,并不是靜態(tài)類型。
  2. 如果獲取到并且訪問(wèn)權(quán)限也是ok的,就直接調(diào)用并返回,如果獲取不到按照繼承體系進(jìn)行查找,找到就調(diào)用。

比較方法重寫和方法重載,我們得到這樣一個(gè)結(jié)論,方法重載是靜態(tài)的,是編譯期行為,方法重寫是動(dòng)態(tài)的,是運(yùn)行期行為。

動(dòng)態(tài)分派的一個(gè)最直接的例子是重寫。對(duì)于重寫,我們已經(jīng)很熟悉了,那么Java虛擬機(jī)是如何在程序運(yùn)行期間確定方法的執(zhí)行版本的呢?

解釋這個(gè)現(xiàn)象,就不得不涉及Java虛擬機(jī)的invokevirtual指令了,這個(gè)指令的解析過(guò)程有助于我們更深刻理解重寫的本質(zhì)。該指令的具體解析過(guò)程如下:

  1. 找到操作數(shù)棧棧頂?shù)牡谝粋€(gè)元素所指向的對(duì)象的實(shí)際類型,記為C

2 . 如果在類型C中找到與常量中描述符和簡(jiǎn)單名稱都相符的方法,則進(jìn)行訪問(wèn)權(quán)限的校驗(yàn),如果通過(guò)則返回這個(gè)方法的直接引用,查找結(jié)束;如果不通過(guò),則返回非法訪問(wèn)異常

  1. 如果在類型C中沒(méi)有找到,則按照繼承關(guān)系從下到上依次對(duì)C的各個(gè)父類進(jìn)行第2步的搜索和驗(yàn)證過(guò)程

  2. 如果始終沒(méi)有找到合適的方法,則拋出抽象方法錯(cuò)誤的異常

從這個(gè)過(guò)程可以發(fā)現(xiàn),在第一步的時(shí)候就在運(yùn)行期確定接收對(duì)象(執(zhí)行方法的所有者程稱為接受者)的實(shí)際類型,所以當(dāng)調(diào)用invokevirtual指令就會(huì)把運(yùn)行時(shí)常量池中符號(hào)引用解析為不同的直接引用,這就是方法重寫的本質(zhì)。


虛方法表和動(dòng)態(tài)分派機(jī)制:
針對(duì)于方法調(diào)用動(dòng)態(tài)分派的過(guò)程:
虛擬機(jī)會(huì)在類的方法區(qū)建立一個(gè)虛方法表的數(shù)據(jù)結(jié)構(gòu)(virtual method table)簡(jiǎn)稱vtable,
針對(duì)于invokeinterface指令來(lái)說(shuō),虛擬機(jī)會(huì)建立一個(gè)接口方法表的數(shù)據(jù)結(jié)構(gòu),(interface method table),簡(jiǎn)稱itable。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容