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("吃老鼠");
}
}
分析
-
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,点击输出的地址,就可以看到类的内存布局信息,如图所示:下面是图片信息解释:

__mark- 含义:这是 对象头中的标记字(Mark Word),用于存储对象的运行时信息。
- 作用:
- 存储对象的哈希码(hashCode)
- 锁状态(无锁、偏向锁、轻量级锁、重量级锁)
- 垃圾回收信息(如分代年龄)
- 示例中值为
1的解释:- 通常表示对象处于 无锁状态(biasable, no lock)
- 在 64 位 JVM 中,如果
__mark是1,可能表示:- 对象的哈希码还未被计算(hashCode 为 0)
- 对象是可偏向但未偏向的状态
__metadata_klass-
含义:这是 指向对象所属类元数据的指针。
-
作用:
- 指向该对象的类信息(
InstanceKlass结构) - 包含类的所有元数据:方法、字段、常量池、父类、接口等
- 指向该对象的类信息(
-
示例中的值:
text
bashInstanceKlass for polymorphism/Dog- 表示该对象是
polymorphism.Dog类的实例 - 通过这个指针,JVM 知道这个对象有哪些方法、字段和类型信息
- 表示该对象是
-
查看内存地址
点击左上角Windows -> console,输入mem 0x00000243c9e80f00 2,回车,输入信息如下:bashmem 0x00000243c9e80f00 2 0x00000243c9e80f00: 0x0000000000000001 0x00000243c9e80f08: 0x0000024476cb1520 hsdb>0x0000024476cb1520就是类型指针,点击左上角tools -> Insprctor,输入地址就可以查看具体信息,如图所示:

-
查看虚方法表
JVM使用C++实现的,多态的实现机制与C++的差不多,都会维护一份虚函数表,这个表记录了多态函数的地址。根据类的基地址加上1B8就是虚函数表的地址,即1520+1B8 = 16D8,将上图Inspector中的地址后四位改成16D8,在windows -> console中输入mem 0x0000024476cb16D8 6,这里的6是前面类内存布局中的虚函数表大小 ,回车后就可以看到虚函数表的函数详细地址,bashmem 0x0000024476cb16D8 6 0x0000024476cb16d8: 0x00000244768b1b10 0x0000024476cb16e0: 0x00000244768b15e8 0x0000024476cb16e8: 0x0000024476cb0a70 0x0000024476cb16f0: 0x00000244768b1540 0x0000024476cb16f8: 0x00000244768b1678 0x0000024476cb1700: 0x0000024476cb14c8 hsdb> -
查表
Dog类继承Animal,点击tools -> Class Brower输入Dog,就可以看到该类具体有哪些方法,如图所示:

在调用方法时会通过对象找到对应的Class类,通过Class类找到虚方法表,最终表中函数所对应的地址去调用Dog类中的eat方法。
总结
当调用实例方法时,会执行<a href="https://docs.oracle.com/javase/specs/jvms/se19/html/jvms-6.html#jvms-6.5.invokevirtual">invokevirtual</a>指令,
-
先通过栈帧中的对象引用找到对象
-
根据对象头获取到对象实际的Class(即JVM内部的Klass对象)
-
获取Class结构找到虚函数表
vtable,虚方法表会在链接阶段根据方法重写规则生成 -
查表得到方法的具体地址
-
执行方法的字节码
