java 運行時多態在 jvm 層面的實現

實驗環境
Window 10
java version “1.8.0_191”
Java™ SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot™ 64-Bit Server VM (build 25.191-b12, mixed mode)

jvm 探測工具

Serviceability Agent 使用簡介在此。需要先掌握這個工具的使用。
sa-jdi.jarC:\Program Files\Java\jdk1.8.0_191\lib 目錄下。
java -cp sa-jdi.jar sun.jvm.hotspot.HSDB 啓動這個工具。
注意:需要把 C:\Program Files\Java\jdk1.8.0_191\bin\sawindbg.dll 複製到 C:\Program Files\Java\jre1.8.0_191\bin。否則啓動報錯。

測試代碼

  1. polymorphic.Animal
package polymorphic;
class Animal {
    public void say() {
        System.out.println("Animal say");
    }
    public void play() {
        System.out.println("play...");
    }
}
  1. polymorphic.Dog
package polymorphic;
class Dog extends Animal {
	public static void testStatic(){
		System.out.println("testStatic");
	}
    public void say() {
        System.out.println("Dog say");
    }
}
  1. polymorphic.Demo
package polymorphic;
import java.util.concurrent.CountDownLatch;
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Animal a = new Animal(); // 1
        a.say();
        Dog d = new Dog(); // 2
        d.say();
        Animal ad = new Dog(); // 3 見字節碼部分
        ad.say();
        CountDownLatch latch = new CountDownLatch(10);// 不讓 main 線程退出,便於調試
        latch.await();
    }
}

字節碼層面分析多態

Classfile /C:/Users/young/Desktop/hahaha/polymorphic/bin/polymorphic/Demo.class
  Last modified 2020-5-30; size 818 bytes
  MD5 checksum db790d091c7918640e6b11ad1b467d17
  Compiled from "Demo.java"
public class polymorphic.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // polymorphic/Demo
   #2 = Utf8               polymorphic/Demo
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lpolymorphic/Demo;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               Exceptions
  #17 = Class              #18            // java/lang/InterruptedException
  #18 = Utf8               java/lang/InterruptedException
  #19 = Class              #20            // polymorphic/Animal
  #20 = Utf8               polymorphic/Animal
  #21 = Methodref          #19.#9         // polymorphic/Animal."<init>":()V
  #22 = Methodref          #19.#23        // polymorphic/Animal.say:()V
  #23 = NameAndType        #24:#6         // say:()V
  #24 = Utf8               say
  #25 = Class              #26            // polymorphic/Dog
  #26 = Utf8               polymorphic/Dog
  #27 = Methodref          #25.#9         // polymorphic/Dog."<init>":()V
  #28 = Methodref          #25.#23        // polymorphic/Dog.say:()V
  #29 = Class              #30            // java/util/concurrent/CountDownLatch
  #30 = Utf8               java/util/concurrent/CountDownLatch
  #31 = Methodref          #29.#32        // java/util/concurrent/CountDownLatch."<init>":(I)V
  #32 = NameAndType        #5:#33         // "<init>":(I)V
  #33 = Utf8               (I)V
  #34 = Methodref          #29.#35        // java/util/concurrent/CountDownLatch.await:()V
  #35 = NameAndType        #36:#6         // await:()V
  #36 = Utf8               await
  #37 = Utf8               args
  #38 = Utf8               [Ljava/lang/String;
  #39 = Utf8               a
  #40 = Utf8               Lpolymorphic/Animal;
  #41 = Utf8               d
  #42 = Utf8               Lpolymorphic/Dog;
  #43 = Utf8               ad
  #44 = Utf8               latch
  #45 = Utf8               Ljava/util/concurrent/CountDownLatch;
  #46 = Utf8               SourceFile
  #47 = Utf8               Demo.java
{
  public polymorphic.Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lpolymorphic/Demo;

  public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Exceptions:
      throws java.lang.InterruptedException
    Code:
      stack=3, locals=5, args_size=1
         0: new           #19                 // class polymorphic/Animal
         3: dup
         4: invokespecial #21                 // Method polymorphic/Animal."<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #22                 // Method polymorphic/Animal.say:()V  // 1
        12: new           #25                 // class polymorphic/Dog
        15: dup
        16: invokespecial #27                 // Method polymorphic/Dog."<init>":()V
        19: astore_2
        20: aload_2
        21: invokevirtual #28                 // Method polymorphic/Dog.say:()V // 2
        24: new           #25                 // class polymorphic/Dog
        27: dup
        28: invokespecial #27                 // Method polymorphic/Dog."<init>":()V
        31: astore_3
        32: aload_3
        33: invokevirtual #22                 // Method polymorphic/Animal.say:()V // 3
        36: new           #29                 // class java/util/concurrent/CountDownLatch
        39: dup
        40: bipush        10
        42: invokespecial #31                 // Method java/util/concurrent/CountDownLatch."<init>":(I)V
        45: astore        4
        47: aload         4
        49: invokevirtual #34                 // Method java/util/concurrent/CountDownLatch.await:()V
        52: return
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 12
        line 8: 20
        line 9: 24
        line 10: 32
        line 11: 36
        line 12: 47
        line 13: 52
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      53     0  args   [Ljava/lang/String;
            8      45     1     a   Lpolymorphic/Animal;
           20      33     2     d   Lpolymorphic/Dog;
           32      21     3    ad   Lpolymorphic/Animal;
           47       6     4 latch   Ljava/util/concurrent/CountDownLatch;
}
SourceFile: "Demo.java"

只需要看字節碼 Code 部分:

  1. 9: invokevirtual #22 // Method polymorphic/Animal.say:()V // 1 對應 a.say(), a 是 Animal 類型
  2. 33: invokevirtual #22 // Method polymorphic/Animal.say:()V // 3 對應 ad.say(), ad 是 Dog 類型

注意 invokevirtual調用的方法不是 static 或者 final 類型的。原因很簡單,final 類型的方法,根本不允許被繼承,自然不會有多態這種東西,因爲編譯器就能確定調用的是哪一個 final方法。static 類型的方法也類似。
看到了嗎?aad 雖然是不同類型,但是 a.say() 和 ad.say()字節碼層面調用的都是 Animal.say()ad 的引用是 Animal 類型。
編譯時無法做到 Animal 調用 Animal 的 say 方法,Dog 調用 Dog 的 say 方法。但是實際的運行結果卻是Animal 調用 Animal 的 say 方法,Dog 調用 Dog 的 say方法。 這是怎樣做到的呢?等程序跑起來,運行起來搞定的。這就是所謂的運行時的多態。實現的思路非常簡單:每一個類都有一個自己的方法表。

Animal 和 Dog 的方法表

Object 類非 static 非 final 方法

一共有 5 個:

  1. public native int hashCode();
  2. public boolean equals(Object obj) { return (this == obj); }
  3. protected native Object clone() throws CloneNotSupportedException;
  4. public String toString() { return getClass().getName() + “@” + Integer.toHexString(hashCode()); }
  5. protected void finalize() throws Throwable { }
SA 查看 Animal 的方法表
Eclipse 啓動 Demo 這個 Main 類

終端 jps 查看 java 進程,如下:

C:\Users\young>jps
17664
19572 Demo  # demo 進程
4308 HSDB  # SA 進程
20232 Jps
Attach Demo 這個進程進行調試
  1. HotSpot Debugger 界面在這裏插入圖片描述

  2. Attach Demo 進程進行調試
    在這裏插入圖片描述

  3. 看到 Demo 的所有線程
    在這裏插入圖片描述

Animal 類的方法表

我們知道 jvm 啓動時,會加載需要的 class。這意味着什麼呢?class 文件只是個二進制文件,所謂加載到 jvm 就是將這些二進制數據轉換成結構化數據。每個 Java 的 Class 在 JVM 內部都會有一個自己的 instanceKlass,這個 instanceKlass 大小在 64 bit hotspot 下是 0x1b8。 類的非 static 或者非 final 方法表,就分配在這個 instanceKlass 的後面。

  1. Class Browser 查看 Animal 類在 jvm 中的內存地址
    Class Browser 在 Tools 菜單下。
    在這裏插入圖片描述

  2. Inspector 查看 Animal Class 的具體信息
    Class Browser 得到 Animal 類的 jvm 中的內存地址
    在這裏插入圖片描述
    如上圖可以看到 _vtable_len 7。也就是 Animal 有 7 個非 static 或者非 final 的方法。爲啥是 7 個呢?很簡單 Object 有 5 個,Animal 中定義了 2 個,所以一共 7 個。
    根據 Class 在 jvm 中的內存地址計算方法表的位置:
    方法表的位置 = 0x0000000100060218(Animal Class 的內存地址) + 0x1b8(instanceKlass 大小) = 0x1000603d0

  3. console 查看 Animal 的方法表
    在這裏插入圖片描述

Dog 類的方法表

查看 Dog 類的方法表,和查看 Animal 的過程一樣。在這裏插入圖片描述

Animal 和 Dog 運行時多態分析

Animal:a
mem 0x1000603d0 7
Animal _v_table 方法表

0x00000001000603d0: 0x0000000017790bd8 
0x00000001000603d8: 0x00000000177906d0 
0x00000001000603e0: 0x0000000017790820 
0x00000001000603e8: 0x0000000017790628 
0x00000001000603f0: 0x0000000017790760 
0x00000001000603f8: 0x0000000017b906c8   # say
0x0000000100060400: 0x0000000017b90770   # play

Dog:ad
mem 0x1000605d0 7
Dog _v_table 方法表

0x00000001000605d0: 0x0000000017790bd8 
0x00000001000605d8: 0x00000000177906d0 
0x00000001000605e0: 0x0000000017790820 
0x00000001000605e8: 0x0000000017790628 
0x00000001000605f0: 0x0000000017790760 
0x00000001000605f8: 0x0000000017b90b08    # say 重寫了,這個地址是 Dog 類重寫的 say 方法的地址
0x0000000100060600: 0x0000000017b90770    # play 沒有重寫,這個地址是 Animal 的 play 方法的地址

可以看到 Animal 類中 say 方法的地址和 Dog 類中 say 方法的地址根本不一樣。這就是重點。每一個類都握有自己的一個方法表。而這些方法的地址是只有在程序運行起來纔可以得到。這其實就是運行時多態的實現方式,或者叫做運行時綁定。

每一個類都有自己的方法表。子類重寫父類方法,雖然子類和父類的方法名相同。但是由於每一個類都有自己的方法表,所以不同類的對象,在運行時都可以找到自己的類的方法。這就是所謂面向接口編程的基礎。定義了接口,但是有不同的實現類,每一個實現類都有自己實現類的方法表。互不相干。java 程序在運行起來之後,不會找不到自己類實現的方法。

面向接口編程
package poly;
public interface Animal {
    void say();
}
package poly;
public class Cat implements Animal {
	@Override
	public void say() {
		System.out.println("I'm a cat");
	}
}
package poly;
public class Dog implements Animal {
	@Override
	public void say() {
		System.out.println("I'm a dog");
	}
}
package poly;
public class Demo {
   public static void main(String[] args) {
	Animal cat = new Cat();  // 拿着 Animal 類型的引用就能訪問所有實現 Animal 接口的實現類的 say 方法。
	cat.say();
	cat = new Dog();
	cat.say();
}
}
References
  1. 運行時多態-運行時綁定
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章