JVM 方法的调用
方法的调用不等于方法执行,方法调用阶段的目标是确定被调用的是哪一个方法,所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载阶段,会将其中一部分符号引用转化为直接引用,这种解析能成立的条件是:方法在程序运行前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。这类方法的调用称为解析。
调用方法的指令:
invokestatic:Invoke a class (static
) method
invokespecial:Invoke instance method; special handling for superclass, private, and instance initialization method invocations父类方法(即super.methodName()这种调用方式)、私有方法和构造方法
invokevirtual:Invoke instance method; dispatch based on class
invokeinterface:Invoke interface method
invokedynamic:Invoke dynamic method
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本、包括静态方法、私有方法、构造方法和父类方法,这些方法在类加载的时候就会把符号引用解析为直接引用。
一、静态分派
看下面的代码及其输出结果:
public class StaticDispatch { static class Animal {} static class Cat extends Animal{} static class Dog extends Animal {} public void method(Animal animal) { System.out.println("animal"); } public void method(Cat cat) { System.out.println("cat"); } public void method(Dog dog) { System.out.println("dog"); } public static void main(String[] args) { StaticDispatch ref = new StaticDispatch(); Animal animal = new Animal(); Animal cat = new Cat(); Animal dog = new Dog(); ref.method(animal); //输出animal ref.method(cat); //输出animal ref.method(dog); //输出animal } }
对于Animal cat = new Cat()来说,Animal称为cat变量的静态类型(Static Type),Cat称为cat变量的实际类型(Actual Type),变量本身的静态类型不会改变,但实际类型可以改变,比如cat = new Dog() 可以把cat变量的实际类型变为Dog,静态类型编译期就已可知,而实际类型运行时才可知。
看main方法的字节码:
public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: new #1; //class cc/lixiaohui/demo/dispatch/StaticDispatch 3: dup 4: invokespecial #41; //Method "":()V 7: astore_1 8: new #42; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Animal 11: dup 12: invokespecial #44; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Animal." ":()V 15: astore_2 16: new #45; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Cat 19: dup 20: invokespecial #47; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Cat." ":()V 23: astore_3 24: new #48; //class cc/lixiaohui/demo/dispatch/StaticDispatch$Dog 27: dup 28: invokespecial #50; //Method cc/lixiaohui/demo/dispatch/StaticDispatch$Dog." ":()V 31: astore 4 33: aload_1 34: aload_2 35: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 38: aload_1 39: aload_3 40: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 43: aload_1 44: aload 4 46: invokevirtual #51; //Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V 49: return LineNumberTable: line 27: 0 line 29: 8 line 30: 16 line 31: 24 line 33: 33 line 34: 38 line 35: 43 line 36: 49 LocalVariableTable: Start Length Slot Name Signature 0 50 0 args [Ljava/lang/String; 8 42 1 ref Lcc/lixiaohui/demo/dispatch/StaticDispatch; 16 34 2 animal Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; 24 26 3 cat Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; 33 17 4 dog Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal; }
可以看到35-46行中有三个invokevirtual指令就是分别调用三个方法的动作,可以看到其目标方法都是一样的:Method method:(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V。
invokevirtual指令
参数 ..., objectref, [arg1, [arg2 ...]] →
Invoke instance method; dispatch based on class,调用实例方法
Let C be the class of objectref. The actual method to be invoked is selected by the following lookup procedure:
- If C contains a declaration for an instance method
m
that overrides (§5.4.5) the resolved method, thenm
is the method to be invoked, and the lookup procedure terminates. - Otherwise, if C has a superclass, this same lookup procedure is performed recursively using the direct superclass of C; the method to be invoked is the result of the recursive invocation of this lookup procedure.
- Otherwise, an
AbstractMethodError
is raised.
因此,在上面代码调用过程中,objectref就是ref,jvm去ref引用对象的类(也就是StaticDispatch类)中找描述符为(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Animal;)V的方法,很明显方法method(Animal animal)符合要求,而method(Cat cat)的描述符为(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Cat;)V,method(Dog dog)的描述符为(Lcc/lixiaohui/demo/dispatch/StaticDispatch$Dog;)V,都不符合要求。
重载时是通过静态类型而不是实际类型作为分派查找的依据。
二、动态分派
看代码及其结果:
public class DynamicDispatch { static class Animal{ void say(){ System.out.println("animal"); } } static class Cat extends Animal { void say() { System.out.println("cat"); } } static class Dog extends Animal { void say() { System.out.println("dog"); } } public static void main(String[] args) { Animal cat = new Cat(); Animal dog = new Dog(); cat.say(); // cat dog.say(); // dog cat = new Dog(); cat.say(); // dog } }
say()方法为实例方法,因此也是invokevirtual指令调用,只要套用上面说的invokevirtual指令的分派过程,就可以知道Java的多态的实现原理,例如当执行cat.say()时(该方法描述符为say:()V),jvm去cat引用的对象的类(也就是实际类型Actual Type),即Cat类中(而不是Animal)中查找描述符相同的方法,很明显可以找到Cat.say()方法,因此该方法被调用。这里继承关系比较简单,不果即使继承关系很复杂,套路还是这个套路,从实际类型开始找,找不到就去父类找,递归地找.....最后没找到就抛异常
重写是通过实际类型而不是静态类型作为分派查找的依据
参考:
《深入理解虚拟机》
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual