面试题答案
一键面试方法调用
- 编译期:在Java中,当通过接口实现类似多继承时,编译器仅依据引用类型来确定可调用的方法集合。例如,若有接口
InterfaceA
与实现该接口的类ClassB
,代码InterfaceA obj = new ClassB();
,编译器会检查InterfaceA
中定义的方法,以此确定obj
可调用的方法。 - 运行期:JVM在运行时根据对象的实际类型来确定具体调用的方法。对于上述例子,实际运行时
obj
指向ClassB
的实例,若ClassB
重写了InterfaceA
中的方法,JVM 会调用ClassB
中重写的方法。这是因为JVM通过对象头中的元数据来获取对象的实际类型,进而在该实际类型的方法表中查找并调用方法。
动态绑定
- 动态绑定概念:动态绑定是指在运行时根据对象的实际类型来确定调用哪个方法的过程。在通过接口实现多态的场景下,这一机制得以体现。
- 实现原理:JVM在加载类时,会为每个类创建一个方法表。方法表中记录了类及其父类(包括接口)中定义的方法,并且方法在方法表中的索引与方法的签名相关。当进行方法调用时,JVM首先获取对象的实际类型,然后在该类型对应的方法表中,根据方法签名找到对应的方法索引,从而调用正确的方法。例如,若有接口
InterfaceA
中有方法void methodA();
,ClassB
实现了InterfaceA
并重写了methodA
,当ClassB
的实例调用methodA
时,JVM会在ClassB
的方法表中找到重写后的methodA
方法并执行。
内存布局
- 对象内存布局:对象在内存中主要由对象头、实例数据和对齐填充三部分组成。对象头包含了对象的运行时元数据,如哈希码、对象分代年龄、锁状态标志等,同时也包含指向对象所属类的元数据的指针。实例数据部分存储了对象的成员变量。对于通过接口实现多态的对象,在内存中其对象头的类指针指向实际实现接口的类的元数据。例如,
ClassB
实现了InterfaceA
,ClassB
的实例对象头中的类指针指向ClassB
的类元数据,而不是InterfaceA
的类元数据。 - 类内存布局:类在内存中存储了类的元数据,包括类的全限定名、超类、实现的接口、字段信息、方法信息等。对于实现接口的类,在类元数据中会记录所实现的接口信息。并且在类加载时,会为类创建方法表,方法表中包含了类及其父类(包括接口)中定义的方法信息。
不同版本JVM中的优化与变化
- 早期JVM版本:早期的JVM在方法调用和动态绑定方面,性能相对较低。例如,早期可能采用较为简单的线性查找方法表的方式来确定方法,这在类继承体系复杂时,查找效率较低。
- 现代JVM版本(如HotSpot JVM):
- 方法内联:现代JVM采用方法内联技术,对于频繁调用的方法,会将方法体直接嵌入到调用处,避免了方法调用的开销,从而提高性能。在多态场景下,若某个实现接口的类中的方法被频繁调用,JVM可能会将该方法内联,提高执行效率。
- 类型继承关系分析(CHA):通过分析类的继承关系,JVM可以在编译期或运行期对方法调用进行优化。例如,若JVM确定某个对象在运行时只有一种实际类型,那么可以将动态绑定优化为静态绑定,直接调用该类型的方法,避免了运行时查找方法表的开销。
- 分层编译:现代JVM采用分层编译技术,对于不同热度的代码采用不同的编译策略。对于刚启动时的代码,采用解释执行,快速启动应用;随着代码执行次数增加,JVM会将热点代码编译成本地机器码,提高执行效率。在多态实现中,对于频繁调用的多态方法,会被编译成本地机器码,提升性能。