JVM是如何执行方法调用的?(上)

重载与重写

重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法。选取的过程共分为三个阶段:

1.在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing),以及可变长参数的情况下选取重载方法:

2.如果在第1个阶段中没有找到适配的方法,那么在允许自动装拆箱,但不允许可变长参数的情况下选取重载方法;

3.如果在第2个阶段中没有找到适配的方法,那么在允许自动装拆箱以及可变长参数的情况下选取重载方法;

Java程序中的重载与重写:

重载:方法名相同,参数不同;

重写:方法名相同,参数相同;

如果子类定义了与父类中非私有方法同名的方法,而且这两个方法参数类型相同,那么这两个方法之间的关系为:

如果这两个方法都是静态的,那么子类中的方法隐藏了父类中的方法。如果这两个方法都不是静态的,且都不是私有的,那么子类的方法重写了父类中的方法。

Java虚拟机中的重载与重写:

重载:无重载

重写:方法名相同,参数相同,返回值相同;

 

JVM的静态绑定和动态绑定

Java虚拟机识别方法的关键在于类名、方法名以及方法描述符(method descriptor)。方法描述符是由方法的参数类型以及返回类型所构成。在同一个类中,如果同时出现多个名字相同且描述符也相同的方法,那么Java虚拟机会在类的验证阶段报错。

可以看到,Java虚拟机与Java语言不同,它并不限制名字与参数类型相同,但返回类型不同的方法出现在同一个类中,对于调用这些方法的字节码来说,由于字节码所附带的方法描述符包含了返回类型,因此Java虚拟机能够准确地识别目标方法。

Java虚拟机中关于方法重写的判定同样基于方法描述符。如果子类定义了与父类中非私有、非静态的同名方法,那么只有当这两个方法的参数类型以及返回类型一致,Java虚拟机才会判定为重写。

对于Java语言中重写而Java虚拟机中非重写的情况,编译器会通过生成桥接方法来实现Java中的重写语义。

由于对重载方法的区分在编译阶段已经完成,我们可以认为Java虚拟机不存在重载这一概念。因此,在某些文章中,重载也被称为静态绑定(static binding),或者编译时多态(compile-time polymorphism);而重写则被称为动态绑定(dynamic binding)。这个说法在Java虚拟机语境下并非完全正确。这是因为某个类中的重载方法可能被它的子类所重写,因此Java编译器会将所有对非私有实例方法的调用编译为需要动态绑定的类型。

具体来说,Java字节码中与调用相关的指令共有五种。

1.invokestatic:用于调用静态方法。

2.invokespecial:用于调用私有实例方法、构造器,以及使用super关键字调用父类的实例方法或构造器,和所实现接口的默认方法。

3.invokevirtual:用于调用非私有实例方法。

4.invokeinterface:用于调用接口方法。

5.invokedynamic:用于调用动态方法。

 

调用指令的符号引用

在编译过程中,我们并不知道目标方法的具体内存地址。因此,Java编译器会暂时用符号引用来表示该目标方法。这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符。

符号引用存储在class文件的常量池之中。根据目标方法是否为接口方法,这些引用可分为接口符号引用和非接口符号引用。

在执行使用了符号引用的字节码前,Java虚拟机需要解析这些符号引用,并替换为实际引用。

 

对于非接口符号引用,假定该符号引用所指向的类为C,则Java虚拟机会按照如下步骤进行查找。

1.在C中查找符合名字及描述符的方法。

2.如果没有找到,在C中的父类中继续搜索,直至Object类。

3.如果没有找到,在C所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。如果目标方法在间接实现的接口中,则需满足C与该接口之间没有其他符合条件的目标方法。如果有多个符合条件的目标方法,则任意返回其中一个。

从这个解析算法可以看出,静态方法也可以通过子类来调用。此外,子类的静态方法会隐藏(注意与重写区分)父类中的同名、同描述符的静态方法。

 

对与接口符号引用,假定该符号引用所指向的接口为I,则Java虚拟机会按照如下步骤进行查找。

1.在I中查找符合名字及描述符的方法。

2.如果没有找到,在Object类中的公有实例方法中搜索。

3.如果没有找到,则在I的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤3的要求一致。

 

经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引。

总结与实践

Java重写与Java虚拟机中的重写并不一致,但是编译器会通过生成桥接方法来弥补。我们来看一下两个生成桥接方法的例子。你可以通过“javap -v”来查看class文件所包含的方法。

public class Practice1 {
    interface Customer{
        boolean isVIP();
    }

    class Merchant{
        public Number acitonPrice(double price,Customer customer){
            return null;
        }
    }

    class NaiveMerchant extends Merchant{
        @Override
        public Double acitonPrice(double price, Customer customer) {
            return 1.2d;
        }
    }
}

 

主要查看 javap -v Practice1\$NaiveMerchant.class

class Practice1$NaiveMerchant extends Practice1$Merchant
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Fieldref           #7.#23         // Practice1$NaiveMerchant.this$0:LPractice1;
   #2 = Methodref          #8.#24         // Practice1$Merchant."<init>":(LPractice1;)V
   #3 = Double             1.2d
   #5 = Methodref          #25.#26        // java/lang/Double.valueOf:(D)Ljava/lang/Double;
   #6 = Methodref          #7.#27         // Practice1$NaiveMerchant.acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
   #7 = Class              #29            // Practice1$NaiveMerchant
   #8 = Class              #31            // Practice1$Merchant
   #9 = Utf8               this$0
  #10 = Utf8               LPractice1;
  #11 = Utf8               <init>
  #12 = Utf8               (LPractice1;)V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               acitonPrice
  #16 = Class              #33            // Practice1$Customer
  #17 = Utf8               Customer
  #18 = Utf8               InnerClasses
  #19 = Utf8               (DLPractice1$Customer;)Ljava/lang/Double;
  #20 = Utf8               (DLPractice1$Customer;)Ljava/lang/Number;
  #21 = Utf8               SourceFile
  #22 = Utf8               Practice1.java
  #23 = NameAndType        #9:#10         // this$0:LPractice1;
  #24 = NameAndType        #11:#12        // "<init>":(LPractice1;)V
  #25 = Class              #34            // java/lang/Double
  #26 = NameAndType        #35:#36        // valueOf:(D)Ljava/lang/Double;
  #27 = NameAndType        #15:#19        // acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
  #28 = Class              #37            // Practice1
  #29 = Utf8               Practice1$NaiveMerchant
  #30 = Utf8               NaiveMerchant
  #31 = Utf8               Practice1$Merchant
  #32 = Utf8               Merchant
  #33 = Utf8               Practice1$Customer
  #34 = Utf8               java/lang/Double
  #35 = Utf8               valueOf
  #36 = Utf8               (D)Ljava/lang/Double;
  #37 = Utf8               Practice1
{
  final Practice1 this$0;
    descriptor: LPractice1;
    flags: ACC_FINAL, ACC_SYNTHETIC

  Practice1$NaiveMerchant(Practice1);
    descriptor: (LPractice1;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this$0:LPractice1;
         5: aload_0
         6: aload_1
         7: invokespecial #2                  // Method Practice1$Merchant."<init>":(LPractice1;)V
        10: return
      LineNumberTable:
        line 12: 0

  public java.lang.Double acitonPrice(double, Practice1$Customer);
    descriptor: (DLPractice1$Customer;)Ljava/lang/Double;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=3
         0: ldc2_w        #3                  // double 1.2d
         3: invokestatic  #5                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         6: areturn
      LineNumberTable:
        line 15: 0

  public java.lang.Number acitonPrice(double, Practice1$Customer);
    descriptor: (DLPractice1$Customer;)Ljava/lang/Number;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=4, locals=4, args_size=3
         0: aload_0
         1: dload_1
         2: aload_3
         3: invokevirtual #6                  // Method acitonPrice:(DLPractice1$Customer;)Ljava/lang/Double;
         6: areturn
      LineNumberTable:
        line 12: 0
}
SourceFile: "Practice1.java"
InnerClasses:
     static #17= #16 of #28; //Customer=class Practice1$Customer of class Practice1
     #30= #7 of #28; //NaiveMerchant=class Practice1$NaiveMerchant of class Practice1
     #32= #8 of #28; //Merchant=class Practice1$Merchant of class Practice1

 

在81行,调用返回值为Number的acitonPrice时,调用了返回值为Double的actionPrice,此为桥接方式。

 

public class Practice2 {
    interface Customer{
        boolean isVIP();
    }
    interface VIP extends Customer{ }

    class Merchant<T extends Customer>{
        public double acitonPrice(double price,T customer){
            return 1.2d;
        }
    }

    class VIPOnlyMerchant extends Merchant<VIP>{
        @Override
        public double acitonPrice(double price, VIP customer) {
            return super.acitonPrice(price, customer);
        }
    }
}

 

执行 javap -v Practice2\$VIPOnlyMerchant.class

 

class Practice2$VIPOnlyMerchant extends Practice2$Merchant<Practice2$VIP>
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Fieldref           #6.#26         // Practice2$VIPOnlyMerchant.this$0:LPractice2;
   #2 = Methodref          #7.#27         // Practice2$Merchant."<init>":(LPractice2;)V
   #3 = Methodref          #7.#28         // Practice2$Merchant.acitonPrice:(DLPractice2$Customer;)D
   #4 = Class              #30            // Practice2$VIP
   #5 = Methodref          #6.#31         // Practice2$VIPOnlyMerchant.acitonPrice:(DLPractice2$VIP;)D
   #6 = Class              #32            // Practice2$VIPOnlyMerchant
   #7 = Class              #34            // Practice2$Merchant
   #8 = Utf8               this$0
   #9 = Utf8               LPractice2;
  #10 = Utf8               <init>
  #11 = Utf8               (LPractice2;)V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               acitonPrice
  #15 = Utf8               VIP
  #16 = Utf8               InnerClasses
  #17 = Utf8               (DLPractice2$VIP;)D
  #18 = Class              #35            // Practice2$Customer
  #19 = Utf8               Customer
  #20 = Utf8               (DLPractice2$Customer;)D
  #21 = Utf8               Signature
  #22 = Utf8               Merchant
  #23 = Utf8               LPractice2$Merchant<LPractice2$VIP;>;
  #24 = Utf8               SourceFile
  #25 = Utf8               Practice2.java
  #26 = NameAndType        #8:#9          // this$0:LPractice2;
  #27 = NameAndType        #10:#11        // "<init>":(LPractice2;)V
  #28 = NameAndType        #14:#20        // acitonPrice:(DLPractice2$Customer;)D
  #29 = Class              #36            // Practice2
  #30 = Utf8               Practice2$VIP
  #31 = NameAndType        #14:#17        // acitonPrice:(DLPractice2$VIP;)D
  #32 = Utf8               Practice2$VIPOnlyMerchant
  #33 = Utf8               VIPOnlyMerchant
  #34 = Utf8               Practice2$Merchant
  #35 = Utf8               Practice2$Customer
  #36 = Utf8               Practice2
{
  final Practice2 this$0;
    descriptor: LPractice2;
    flags: ACC_FINAL, ACC_SYNTHETIC

  Practice2$VIPOnlyMerchant(Practice2);
    descriptor: (LPractice2;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this$0:LPractice2;
         5: aload_0
         6: aload_1
         7: invokespecial #2                  // Method Practice2$Merchant."<init>":(LPractice2;)V
        10: return
      LineNumberTable:
        line 13: 0

  public double acitonPrice(double, Practice2$VIP);
    descriptor: (DLPractice2$VIP;)D
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=3
         0: aload_0
         1: dload_1
         2: aload_3
         3: invokespecial #3                  // Method Practice2$Merchant.acitonPrice:(DLPractice2$Customer;)D
         6: dreturn
      LineNumberTable:
        line 16: 0

  public double acitonPrice(double, Practice2$Customer);
    descriptor: (DLPractice2$Customer;)D
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=4, locals=4, args_size=3
         0: aload_0
         1: dload_1
         2: aload_3
         3: checkcast     #4                  // class Practice2$VIP
         6: invokevirtual #5                  // Method acitonPrice:(DLPractice2$VIP;)D
         9: dreturn
      LineNumberTable:
        line 13: 0
}
Signature: #23                          // LPractice2$Merchant<LPractice2$VIP;>;
SourceFile: "Practice2.java"
InnerClasses:
     static #15= #4 of #29; //VIP=class Practice2$VIP of class Practice2
     static #19= #18 of #29; //Customer=class Practice2$Customer of class Practice2
     #22= #7 of #29; //Merchant=class Practice2$Merchant of class Practice2
     #33= #6 of #29; //VIPOnlyMerchant=class Practice2$VIPOnlyMerchant of class Practice2

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值