JVM-方法调用

内存角度

方法的执行原理:每一次方法调用都会生成一个栈帧并压入栈中,方法链的执行就是一个个栈帧弹出栈的过程。

方法调用指令

JVM方法调用原理

Java中有非虚方法和虚方法,前者是指在解析阶段可以确定的唯一的调用版本,如静态方法、构造器方法、父类方法(特指在子类中使用super调用,而不是在客户端使用对象引用调用)、私有方法(上述几种方法是使用invokestatic和invokespecial指令调用的)以及被final修饰的方法(使用invokevirtual调用),这些方法在类加载阶段就会把方法的符号引用解析为直接引用;除此之外的都是虚方法,虚方法则只能在运行期进行分派调用。

Java中方法的调用对应字节码有5条指令:

  • **解析**调用:在编译期就可知的,有一个确定的版本且运行期不可变
    • invokestatic:用于调用**静态**方法。
    • invokespecial:用于调用实例**构造器方法、私有方法和父类方法(super**调用)。
  • **分派**调用
    • invokevirtual:用于调用所有的**虚方法**。
    • invokeinterface:用于调用**接口方法**,会在运行时再确定一个实现该接口的对象。
    • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。

invokedynamic与前4条指令不同的是,该指令分派的逻辑是由用户指定,用于支持动态类型语言特性。

分派调用的实现

  • 方法表:虚拟机在方法区会为**每个类型建立一个虚方法表**(支持invokevirtual 指令)以及接口方法表(支持invokeinterface指令),方法表中存的是各个方法的实际入口地址,如果子类没有重写父类中的方法,那么父子类指向同一个地址,否则,子类就会指向自己重写后的方法入口地址
  • 实现过程:访问栈上的调用者,读取调用者的动态类型,读取该类型的方法表,读取方法表中某个索引值所对应的目标方法。
  • 在方法表的基础上,动态绑定的优化技术有**内联缓存方法内联**

分派调用总结

分派又分为静态分派、动态分派:

  • **重载属于静态分派,在编译期间能根据参数静态类型**决定使用哪一版本的方法,编译期间调用的对象是可以确定的

  • **重写属于动态分派,在运行期间进行符号引用解析,根据接收者**的实际类型决定调用方法的版本。

  • 解析和分派时多态的一个实现,这种多态是方法的多态。但是字段不支持多态,父类字段名和子类字段名相同时,子类对象会存在两个字段,但是他们会被分开在所属类的空间中,父类方法访问的时父类的字段,子类方法访问的是子类的字段。

  • 静态分派、动态分派是JVM的方法调用的两个步骤:

    • 概念:JVM识别方法的关键在于**类名方法名以及方法描述符**,方法描述符是由方法的参数类型以及返回类型所构成
    • 类名)先确定调用者是谁,获取到调用者的**实际类型,然后找到调用者实际类型的方法表,这是动态分派**(方法重写)的过程
    • 方法名方法描述符)根据方法名以及方法描述符,在方法表中查找方法引用,这是**静态分派**(方法重载)的过程