實驗環境
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.jar
在 C:\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
。否則啓動報錯。
測試代碼
- polymorphic.Animal
package polymorphic;
class Animal {
public void say() {
System.out.println("Animal say");
}
public void play() {
System.out.println("play...");
}
}
- polymorphic.Dog
package polymorphic;
class Dog extends Animal {
public static void testStatic(){
System.out.println("testStatic");
}
public void say() {
System.out.println("Dog say");
}
}
- 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 部分:
9: invokevirtual #22 // Method polymorphic/Animal.say:()V
// 1 對應 a.say(), a 是 Animal 類型33: invokevirtual #22 // Method polymorphic/Animal.say:()V
// 3 對應 ad.say(), ad 是 Dog 類型
注意 invokevirtual
調用的方法不是 static
或者 final
類型的。原因很簡單,final 類型的方法,根本不允許被繼承,自然不會有多態這種東西,因爲編譯器就能確定調用的是哪一個 final
方法。static
類型的方法也類似。
看到了嗎?a
和 ad
雖然是不同類型,但是 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 個:
- public native int hashCode();
- public boolean equals(Object obj) { return (this == obj); }
- protected native Object clone() throws CloneNotSupportedException;
- public String toString() { return getClass().getName() + “@” + Integer.toHexString(hashCode()); }
- 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 這個進程進行調試
-
HotSpot Debugger 界面
-
Attach Demo 進程進行調試
-
看到 Demo 的所有線程
Animal 類的方法表
我們知道 jvm 啓動時,會加載需要的 class。這意味着什麼呢?class 文件只是個二進制文件,所謂加載到 jvm 就是將這些二進制數據轉換成結構化數據。每個 Java 的 Class 在 JVM 內部都會有一個自己的 instanceKlass,這個 instanceKlass 大小在 64 bit hotspot 下是 0x1b8
。 類的非 static 或者非 final 方法表,就分配在這個 instanceKlass 的後面。
-
Class Browser 查看 Animal 類在 jvm 中的內存地址
Class Browser 在 Tools 菜單下。
-
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
-
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();
}
}