JVM 方法的调用

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 "<init>":()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."<init>":()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."<init>":()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."<init>":()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, then m 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

jvm

相关推荐