面试题答案
一键面试Java多态运行时对象内存布局、方法表协同工作原理
- JVM内存模型相关区域
- 堆(Heap):对象实例都在堆中分配内存。在Java多态场景下,不同类型的对象(父类引用指向子类实例)都存储于堆中。例如,
Animal animal = new Dog();
,Dog
对象在堆中创建,它包含了Dog
类自身以及从Animal
类继承的成员变量。 - 方法区(Method Area):存储类的元数据,包括类的结构信息(如字段、方法等)、常量池等。对于多态涉及的类,其类信息都在方法区,比如
Animal
类和Dog
类的元数据。其中,方法区中还包含了方法表,方法表用于快速定位方法。
- 堆(Heap):对象实例都在堆中分配内存。在Java多态场景下,不同类型的对象(父类引用指向子类实例)都存储于堆中。例如,
- 对象内存布局
- 对象头:包含两部分,一部分是Mark Word,用于存储对象的运行时元数据,如哈希码、对象分代年龄、锁状态标志等信息;另一部分是类型指针,指向方法区中该对象所属类的元数据,通过这个指针可以找到类的方法表等信息。在多态中,不同子类对象的对象头中的类型指针指向各自对应的类元数据。例如,
Dog
对象的类型指针指向Dog
类在方法区的元数据。 - 实例数据:存储对象的成员变量,包括从父类继承来的和子类自己定义的。例如
Dog
对象的实例数据包含从Animal
类继承的name
等变量,以及Dog
类自己可能定义的barkVolume
等变量。 - 对齐填充:不是必须的,由于JVM要求对象起始地址必须是8字节的整数倍,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
- 对象头:包含两部分,一部分是Mark Word,用于存储对象的运行时元数据,如哈希码、对象分代年龄、锁状态标志等信息;另一部分是类型指针,指向方法区中该对象所属类的元数据,通过这个指针可以找到类的方法表等信息。在多态中,不同子类对象的对象头中的类型指针指向各自对应的类元数据。例如,
- 方法表
- 方法表结构:方法表是类在方法区的元数据的一部分,它是一个数组,每个元素是一个指向方法在方法区内存位置的指针。在多态中,子类的方法表会包含从父类继承的方法以及子类重写或新增的方法。例如,
Dog
类的方法表中,对于重写的makeSound
方法,其指针指向Dog
类中重写后的makeSound
方法的实现;对于从Animal
类继承且未重写的方法,指针指向Animal
类中该方法的实现。 - 方法表的构建:在类加载的链接阶段,JVM会为每个类构建方法表。对于父类的方法,子类方法表中会继承,如果子类重写了该方法,方法表中对应位置的指针会更新为指向子类重写的方法。这样在运行时,通过对象的类型指针找到类的方法表,就能根据方法表中指针快速定位到实际要执行的方法。
- 方法表结构:方法表是类在方法区的元数据的一部分,它是一个数组,每个元素是一个指向方法在方法区内存位置的指针。在多态中,子类的方法表会包含从父类继承的方法以及子类重写或新增的方法。例如,
- 多态特性的实现
- 编译期:Java编译器在编译时,根据引用类型(即声明的变量类型)来确定可以调用的方法。例如,
Animal animal = new Dog();
,编译器只知道animal
是Animal
类型,所以只能确定Animal
类中定义的方法可以调用。但是对于重写的方法,编译器会做一些标记,以便运行时能够正确调用子类的方法。 - 运行期:当实际执行方法调用时,JVM首先根据对象的对象头中的类型指针找到对象所属类的方法表。然后在方法表中查找要调用的方法对应的指针,找到后调用该指针指向的实际方法。例如,
animal.makeSound();
,运行时JVM根据Dog
对象的类型指针找到Dog
类的方法表,由于makeSound
方法被重写,方法表中makeSound
方法的指针指向Dog
类重写后的makeSound
方法实现,从而实现多态特性,调用的是Dog
类的makeSound
方法,而不是Animal
类的。
- 编译期:Java编译器在编译时,根据引用类型(即声明的变量类型)来确定可以调用的方法。例如,