欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM 方法的调用

程序员文章站 2022-05-20 10:14:25
...

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