概述

JVM如何执行方法调用 - 图1

方法查找

对于非接口符号引用,假定该符号引用所指向的类为 C,则 Java 虚拟机会按照如下步骤进行查找。
在 C 中查找符合名字及描述符的方法。
如果没有找到,在 C 的父类中继续搜索,直至 Object 类。
如果没有找到,在 C 所直接实现或间接实现的接口中搜索,这一步搜索得到的目标方法必须是非私有、非静态的。并且,如果目标方法在间接实现的接口中,则需满足 C 与该接口之间没有其他符合条件的目标方法。如果有多个符合条件的目标方法,则任意返回其中一个。
从这个解析算法可以看出,静态方法也可以通过子类来调用。此外,子类的静态方法会隐藏(注意与重写区分)父类中的同名、同描述符的静态方法。

对于接口符号引用,假定该符号引用所指向的接口为 I,则 Java 虚拟机会按照如下步骤进行查找。在 I 中查找符合名字及描述符的方法。如果没有找到,在 Object 类中的公有实例方法中搜索。如果没有找到,则在 I 的超接口中搜索。这一步的搜索结果的要求与非接口符号引用步骤 3 的要求一致。经过上述的解析步骤之后,符号引用会被解析成实际引用。对于可以静态绑定的方法调用而言,实际引用是一个指向方法的指针。对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引.

动态方法表

方法表本质上是一个数组,每个数组元素指向一个当前类及其祖先类中非私有的实例方法。
这些方法可能是具体的、可执行的方法,也可能是没有相应字节码的抽象方法。方法表满足两个特质:其一,子类方法表中包含父类方法表中的所有方法;其二,子类方法在方法表中的索引值,与它所重写的父类方法的索引值相同。
方法调用指令中的符号引用会在执行之前解析成实际引用。对于静态绑定的方法调用而言,实际引用将指向具体的目标方法。对于动态绑定的方法调用而言,实际引用则是方法表的索引值(实际上并不仅是索引值)

在执行过程中,Java 虚拟机将获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法。这个过程便是动态绑定。
代码示例:

  1. abstract class Passenger {
  2. abstract void passThroughImmigration();
  3. @Override
  4. public String toString() { ... }
  5. }
  6. class ForeignerPassenger extends Passenger {
  7. @Override
  8. void passThroughImmigration() { /* 进外国人通道 */ }
  9. }
  10. class ChinesePassenger extends Passenger {
  11. @Override
  12. void passThroughImmigration() { /* 进中国人通道 */ }
  13. void visitDutyFreeShops() { /* 逛免税店 */ }
  14. }
  15. Passenger passenger = ...
  16. passenger.passThroughImmigration();

image.png

桥接方法

示例

Java编译器自动生成的方法,举一个方法重写的场景。

  1. interface Customer {
  2. boolean isVIP();
  3. }
  4. class VIP implements Customer {
  5. @Override
  6. public boolean isVIP() {
  7. return true;
  8. }
  9. }
  10. class Merchant<T extends Customer> {
  11. public double actionPrice(double price, T customer) {
  12. return 0;
  13. }
  14. }
  15. class VIPOnlyMerchant extends Merchant<VIP> {
  16. @Override
  17. public double actionPrice(double price, VIP customer) {
  18. return 0;
  19. }
  20. }
  21. class Main {
  22. public static void main(String[] args) {
  23. Merchant m = new VIPOnlyMerchant();
  24. m.actionPrice(1.0, new VIP());
  25. m.actionPrice(1.0, new Customer() {
  26. @Override
  27. public boolean isVIP() {
  28. return false;
  29. }
  30. });
  31. }
  32. }

javap -v VIPOnlyMerchant.class

编译后的VIPOnlyMerchant,生成了一个桥接方法,方法签名为:

ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC

  1. {
  2. jvm.VIPOnlyMerchant();
  3. descriptor: ()V
  4. flags:
  5. Code:
  6. stack=1, locals=1, args_size=1
  7. 0: aload_0
  8. 1: invokespecial #1 // Method jvm/Merchant."<init>":()V
  9. 4: return
  10. LineNumberTable:
  11. line 40: 0
  12. LocalVariableTable:
  13. Start Length Slot Name Signature
  14. 0 5 0 this Ljvm/VIPOnlyMerchant;
  15. public double actionPrice(double, jvm.VIP);
  16. descriptor: (DLjvm/VIP;)D
  17. flags: ACC_PUBLIC
  18. Code:
  19. stack=2, locals=4, args_size=3
  20. 0: dconst_0
  21. 1: dreturn
  22. LineNumberTable:
  23. line 43: 0
  24. LocalVariableTable:
  25. Start Length Slot Name Signature
  26. 0 2 0 this Ljvm/VIPOnlyMerchant;
  27. 0 2 1 price D
  28. 0 2 3 customer Ljvm/VIP;
  29. MethodParameters:
  30. Name Flags
  31. price
  32. customer
  33. ---- 生成的桥接方法
  34. public double actionPrice(double, jvm.Customer);
  35. descriptor: (DLjvm/Customer;)D
  36. flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
  37. Code:
  38. stack=4, locals=4, args_size=3
  39. 0: aload_0
  40. 1: dload_1
  41. 2: aload_3
  42. 3: checkcast #2 // class jvm/VIP
  43. 6: invokevirtual #3 // Method actionPrice:(DLjvm/VIP;)D
  44. 9: dreturn
  45. LineNumberTable:
  46. line 40: 0
  47. LocalVariableTable:
  48. Start Length Slot Name Signature
  49. 0 10 0 this Ljvm/VIPOnlyMerchant;
  50. MethodParameters:
  51. Name Flags
  52. price synthetic
  53. customer synthetic
  54. }

所以 编译后的VIPOnlyMerchant类有如下两个方法:

  1. public double actionPrice(double price, VIP customer)
  2. public double actionPrice(double price, Customer customer)

所以什么是桥接方法

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, called a bridge method, as part of the type erasure process. You normally don’t need to worry about bridge methods, but you might be puzzled if one appears in a stack trace. see: https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html

所以:基本上可以不用关注