Java多态原理

发布于2026-01-08
字数: 3327
JVM
Java
多态
虚方法表
HSDB

示例代码

java 复制代码
package polymorphism;

import java.io.IOException;

/**
 * 演示多态
 * 运行时需要加上禁用指针压缩JVM参数:-XX:-UseCompressedOops -XX:-UseCompressedClassPointers
 */
public class Polymorphism {

    public static void test(Animal animal) {
        animal.eat();
        System.out.println(animal);
    }

    public static void main(String[] args) throws IOException {
        test(new Cat());
        test(new Dog());
        System.in.read();
    }
}

abstract class Animal {
    public abstract void eat();

    @Override
    public String toString() {
        return "我是" + this.getClass().getSimpleName();
    }
}

class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
}

class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("吃老鼠");
    }
}

分析

  1. jdk8环境切换到jdk安装目录运行java -cp .\lib\sa-jdi.jar sun.jvm.hotspot.HSDB,点击左上角File -> Attach to HotSpot process,输入进程号;随后左上角Tools -> Find Object by Query,输入select d from polymorphism.Dog d,点击输出的地址,就可以看到类的内存布局信息,如图所示:

    下面是图片信息解释:
    image-20260107164914599

    __mark

    • 含义:这是 对象头中的标记字(Mark Word),用于存储对象的运行时信息。
    • 作用
      • 存储对象的哈希码(hashCode)
      • 锁状态(无锁、偏向锁、轻量级锁、重量级锁)
      • 垃圾回收信息(如分代年龄)
    • 示例中值为 1 的解释
      • 通常表示对象处于 无锁状态(biasable, no lock)
      • 在 64 位 JVM 中,如果 __mark1,可能表示:
        • 对象的哈希码还未被计算(hashCode 为 0)
        • 对象是可偏向但未偏向的状态

    __metadata_klass

    • 含义:这是 指向对象所属类元数据的指针

    • 作用

      • 指向该对象的类信息(InstanceKlass 结构)
      • 包含类的所有元数据:方法、字段、常量池、父类、接口等
    • 示例中的值

      text

      bash 复制代码
      InstanceKlass for polymorphism/Dog
      • 表示该对象是 polymorphism.Dog 类的实例
      • 通过这个指针,JVM 知道这个对象有哪些方法、字段和类型信息
  2. 查看内存地址
    点击左上角Windows -> console,输入mem 0x00000243c9e80f00 2,回车,输入信息如下:

    bash 复制代码
    mem 0x00000243c9e80f00 2
    0x00000243c9e80f00: 0x0000000000000001 
    0x00000243c9e80f08: 0x0000024476cb1520 
    
    hsdb> 

    0x0000024476cb1520就是类型指针,点击左上角tools -> Insprctor,输入地址就可以查看具体信息,如图所示:
    image-20260107161949553

  3. 查看虚方法表
    JVM使用C++实现的,多态的实现机制与C++的差不多,都会维护一份虚函数表,这个表记录了多态函数的地址。根据类的基地址加上1B8就是虚函数表的地址,即1520+1B8 = 16D8,将上图Inspector中的地址后四位改成16D8,在windows -> console中输入mem 0x0000024476cb16D8 6,这里的6是前面类内存布局中的虚函数表大小 ,回车后就可以看到虚函数表的函数详细地址,

    bash 复制代码
    mem 0x0000024476cb16D8 6
    0x0000024476cb16d8: 0x00000244768b1b10 
    0x0000024476cb16e0: 0x00000244768b15e8 
    0x0000024476cb16e8: 0x0000024476cb0a70 
    0x0000024476cb16f0: 0x00000244768b1540 
    0x0000024476cb16f8: 0x00000244768b1678 
    0x0000024476cb1700: 0x0000024476cb14c8 
    
    hsdb> 
  4. 查表
    Dog类继承Animal,点击tools -> Class Brower输入Dog,就可以看到该类具体有哪些方法,如图所示:
    image-20260107165749168
    在调用方法时会通过对象找到对应的Class类,通过Class类找到虚方法表,最终表中函数所对应的地址去调用Dog类中的eat方法。

总结

当调用实例方法时,会执行<a href="https://docs.oracle.com/javase/specs/jvms/se19/html/jvms-6.html#jvms-6.5.invokevirtual&quot;&gt;invokevirtual&lt;/a&gt;指令,

  • 先通过栈帧中的对象引用找到对象

  • 根据对象头获取到对象实际的Class(即JVM内部的Klass对象)

  • 获取Class结构找到虚函数表vtable,虚方法表会在链接阶段根据方法重写规则生成

  • 查表得到方法的具体地址

  • 执行方法的字节码