JVM方法调用
方法重载和方法重写
多态是面向对象程序设计的重要特性,方法重载和方法重写是Java语言实现多态的主要形式。方法重载指的是在同一个类中,相同名称的方法可以有不同的参数(包括参数类型和参数个数)来实现不同的功能,如果同一个类中存在多个方法名称相同且参数完全一致,不管各个方法的返回类型是否相同,这个类是不能通过编译的。但是可以通过字节码工具注入返回类型不同的方法,因为在JVM层面,识别一个方法主要是通过类名、方法名和方法签名,其中方法签名就包括参数和返回值类型。
方法重写是最重要的多态实现形式,它允许子类在继承父类的部分功能时(为什么强调是部分功能?因为private是无法继承的),拥有自己独特的行为。
静态绑定和动态绑定
前面说过Java虚拟机是通过类名、方法名和方法签名来识别方法的,同一个类中存在方法名相同且方法签名相同的方法,编译阶段会报错,我们可以认为方法重载的区分在编译阶段已经完成,所以一般认为对于Java虚拟机来说是不存在方法重载的,有的文章将重载定性为静态绑定,也称之为编译时多态。java中重写的判定也是通过方法名和方法签名来完成的,即当子类中定义了非私有非静态和父类相同的方法,JVM才会判定其为重写,方法重写被定性为动态绑定。但是这种说法不是完全正确的,如果子类重写了父类的重载方法,那么java虚拟机会将所有非私有非静态的方法编译为需要动态绑定的类型,所以确切的说,静态绑定指的是在解析阶段能够直接识别的目标方法,而动态绑定指的是需要通过调用者的类型来动态识别目标方法。
符号引用
前面在介绍类的加载过程时介绍过符号引用,在解析阶段需要将符号引用解析为直接引用,那么什么是符号引用?什么又是直接引用?
在编译阶段不知道目标类的具体位置,就用符号引用来表示,符号引用包括目标方法所在的类名或者接口名,方法名和方法签名,符号引用又分为接口符号引用和非接口符号引用,储存在常量池中,可以通过javap -v来查看。在链接阶段可能需要将符号引用转换为直接引用,对于非接口符号引用,如果目标类指向TargetObjectImpl,虚拟机会按照如下步骤去查找:
1)在目标类 TargetObjectImpl 中查找;
2)如果没有找到,在其父类中查找,直至Object类;
3)如果没有找到,在TargetObjectImpl直接或者间接实现的接口中去查找,但是这个过程只能查找非私有非静态的方法。
通过这个查找过程可以看出,子类可以调用父类的静态方法,子类的静态方法会隐藏父类中相同的静态方法。
对于接口符号引用,查找顺序如下:
1)在目标接口中查找目标方法;
2)如果没有找到,在Object的公有实例方法中查找;
3)如果没有找到,在其超接口中去查找,这个过程只能查找非私有非静态的方法。
解析过程中,对于静态绑定,直接引用指向可识别的目标方法,对于动态绑定,直接引用指向一个方法表的索引。
方法调用
从JVM层面来看,和方法调用相关的指令有五个:
1)invokeStatic:用于调用静态方法;
2)invokeSpecial:用于调用私有方法、构造器、super引用父类的方法和构造器、接口中的默认方法(java8 引入的default修饰的方法);
3)invokeVirtual:调用非私有的实例方法;
4)invokeInterface:用于调用接口方法;
5)invokeDynamic:用于调用动态方法
方法表
前面介绍过在类加载的准备阶段除了为静态字段分配内存之外,还会构造与该类相关的方法表,方法表这个数据结构就是一个典型的空间换时间的例子,目的是提高动态绑定的性能。