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               
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."":()V
   #9 = NameAndType        #5:#6          // "":()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."":()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."":()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."":(I)V
  #32 = NameAndType        #5:#33         // "":(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."":()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."":()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."":()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."":()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."":(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 界面Java 运行时多态在 jvm 层面的实现_第1张图片

  2. Attach Demo 进程进行调试
    Java 运行时多态在 jvm 层面的实现_第2张图片

  3. 看到 Demo 的所有线程
    Java 运行时多态在 jvm 层面的实现_第3张图片

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 菜单下。
    Java 运行时多态在 jvm 层面的实现_第4张图片

  2. Inspector 查看 Animal Class 的具体信息
    Class Browser 得到 Animal 类的 jvm 中的内存地址
    Java 运行时多态在 jvm 层面的实现_第5张图片
    如上图可以看到 _vtable_len 7。也就是 Animal 有 7 个非 static 或者非 final 的方法。为啥是 7 个呢?很简单 Object 有 5 个,Animal 中定义了 2 个,所以一共 7 个。
    根据 Class 在 jvm 中的内存地址计算方法表的位置:
    方法表的位置 = 0x0000000100060218(Animal Class 的内存地址) + 0x1b8(instanceKlass 大小) = 0x1000603d0

  3. console 查看 Animal 的方法表
    Java 运行时多态在 jvm 层面的实现_第6张图片

Dog 类的方法表

查看 Dog 类的方法表,和查看 Animal 的过程一样。Java 运行时多态在 jvm 层面的实现_第7张图片

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. 运行时多态-运行时绑定

你可能感兴趣的:(jvm)