2.46 Java方法调用原理

less than 1 minute read

方法调用

  • 方法调用即确定调用方法的版本, 一切方法调用在Class文件存储都是符号引用; 而直接引用则是方法实际内存地址

  • 方法调用指令

    • invokevirtual指令, 用于调用对象的实例方法, 根据对象的实际类型进行分派(虚方法分派)
    • invokeinterface指令, 用于调用接口方法
    • invokespecial指令, 调用需要特殊处理的实例方法, 如实例初始化方法, 私有方法和父类方法
    • invokestatic指令, 调用类方法
    • invokedynamic指令, 用于运行时动态解析出调用点限定符所引用的方法, 并执行该方法, 前面4条指令的分派逻辑固化在Java虚拟机内部, 该方法由用户所设定的引导方法决定

解析

  • 方法在Class文件中都是常量池中的符号引用, 解析是静态过程, 在编译期间可以完全确定, 在类装载的解析阶段把相关符号引用转化成直接引用

分派

  • 静态分派
    • Human man = new Man(), Human为静态类型(外观类型), 而Man为实际类型, 静态类型和实际类型可以发送变化, 但静态类型仅在使用时发生(如强制类型转换)
    • 动态类型变化结果在运行起可确定, 编译器对(对象的)实际类型无感
    • 变量的静态类型不会变改变, 编译器在确定重载函数版本通过参数的静态类型而非实际类型, 编译期间即可确定重载版本; 根据上述结果可验证这一结论
  public class Dispatch {
      static abstract class Human {}
      static class Man extends Human {}
      static class Woman extends Human {}

      public static void main(String args[]) {
        Human man = new Man();
        Human woman = new Woman();
        Dispatch d = new Dispatch();
        // 输出hello human
        d.sayHello(man);
        // 输出hello human
        d.sayHello(woman);
      }

      public void sayHello(Human human) {
        System.out.println("hello human");
      }

      public void sayHello(Man man) {
        System.out.println("hello man");
      }

      public void sayHello(Woman woman) {
        System.out.println("hello woman");
      }
  }
  • 动态分派
    • invokevirtual指令即把常量池的类方法符号引用解析到不同的直接引用上, 该过程即Java方法重写的本质; invokevirtual指令的多态查找过程: 1). 找到操作数栈顶元素锁指向的对象的实际类型, 记为C. 2). 如果在类型C中找到与常量中的描述符和简单名称都相符的方法, 则进行权限校验, 如果通过则返回这个方法的直接引用; 不通过则抛IllegalAccessException异常 3). 否则, 按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程 4). 如果找不到合适的方法, 则抛AbstractMethodError异常
    • 虚拟机动态分派的实现, 在方法区执行时建立虚方法表(Virtual Method Table)

参考