JVM--基础--17--方法调用:解析与分派

JVM–基础–17–方法调用:解析与分派


1、方法调用

  1. 方法调用并不等于方法的执行
  2. 方法调用阶段唯一的任务就是确定被调用方法的版本(考虑多态情况)。

2、方法解析

方法解析:在类加载的方法解析阶段,会将方法一部分的方法符号引用转化为直接引用。

这种方法解析的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期间是不变的。换句话说,调用方法的版本在编译期时就必须确定下来。

2.1、这类方法主要包括:

  1. 静态方法:与类直接关联
  2. 私有方法:后者在外部不可被访问
  3. 这两种方法都不能通过继承或者别的方式重写其他版本,因此他们都适合在类的加载阶段进行解析。

2.2、在Java虚拟机里提供了5种方法调用字节码指令

1. invokestatic:调用静态方法。
2. invokespecial:调用实例构造器< init>方法,私有方法和父类方法。
3. invokevirtual:调用所有的虚方法。()
4. invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
5. invokedynamic:现在运行时动态解析调用点限定符所引用的方法,然后再执行该方法。

只要是能被invokestatic和jinvokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本

符合这个条件的方法有

1. 有静态方法
2. 私有方法
3. 实例构造器
4. 父类方法 

他们类加载的时候就会把符号引用解析为该方法的直接引用。 这些都可以称为非虚方法,与之对应的就是虚方法。

Java种的虚方法除了使用invokestatic,invokespecial调用的方法之外还有一种,就是被final修饰的方法。再Java语言规范中明确说明了final方法是一种非虚方法。

3、分派

  1. 解析调用是一个静态的过程,在编译期间就完全确定,不会延迟到运行期再去完成。
  2. 分派调用:可以是静态的也可以是动态的。根据分派中数量可分为单分派和多分派。这两类分派又可两两组合成:静态单分派,静态多分派,动态单分派和动态多分派
  3. 分派体现了Java的多态性,如"重载"和"重写"。

3.1、静态分派(重载)

3.1.1、代码

//静态分派
public class Test {


        static abstract class Human{

        }

        static class Man extends Human{

        }

        static class Woman extends Human{

        }

        public void sayhello(Human Human){
            System.out.println("Human");
        }

        public void sayhello(Man Man){
            System.out.println("Man");
        }

        public void sayhello(Woman Woman){
            System.out.println("Woman");
        }


        public static void main(String[] args){

            Human man = new Man();
            Human woman = new Woman();
            
            Test t = new Test();
            t.sayhello(man);
            t.sayhello(woman);
        }



}

//输出

Human
Human

3.1.2、对应的字节码

F:\>javap -v Test.class
Classfile /F:/Test.class
  Last modified 2019-7-25; size 807 bytes
  MD5 checksum bf1ef72fc4f554f5db2536efaae70880
  Compiled from "Test.java"
public class Test
  SourceFile: "Test.java"
  InnerClasses:
       static #15= #9 of #11; //Woman=class Test$Woman of class Test
       static #17= #7 of #11; //Man=class Test$Man of class Test
       static abstract #19= #18 of #11; //Human=class Test$Human of class Test
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#32        //  java/lang/Object."<init>":()V
   #2 = Fieldref           #33.#34        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #19            //  Human
   #4 = Methodref          #35.#36        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #17            //  Man
   #6 = String             #15            //  Woman
   #7 = Class              #37            //  Test$Man
   #8 = Methodref          #7.#32         //  Test$Man."<init>":()V
   #9 = Class              #38            //  Test$Woman
  #10 = Methodref          #9.#32         //  Test$Woman."<init>":()V
  #11 = Class              #39            //  Test
  #12 = Methodref          #11.#32        //  Test."<init>":()V
  #13 = Methodref          #11.#40        //  Test.sayhello:(LTest$Human;)V
  #14 = Class              #41            //  java/lang/Object
  #15 = Utf8               Woman
  #16 = Utf8               InnerClasses
  #17 = Utf8               Man
  #18 = Class              #42            //  Test$Human
  #19 = Utf8               Human
  #20 = Utf8               <init>
  #21 = Utf8               ()V
  #22 = Utf8               Code
  #23 = Utf8               LineNumberTable
  #24 = Utf8               sayhello
  #25 = Utf8               (LTest$Human;)V
  #26 = Utf8               (LTest$Man;)V
  #27 = Utf8               (LTest$Woman;)V
  #28 = Utf8               main
  #29 = Utf8               ([Ljava/lang/String;)V
  #30 = Utf8               SourceFile
  #31 = Utf8               Test.java
  #32 = NameAndType        #20:#21        //  "<init>":()V
  #33 = Class              #43            //  java/lang/System
  #34 = NameAndType        #44:#45        //  out:Ljava/io/PrintStream;
  #35 = Class              #46            //  java/io/PrintStream
  #36 = NameAndType        #47:#48        //  println:(Ljava/lang/String;)V
  #37 = Utf8               Test$Man
  #38 = Utf8               Test$Woman
  #39 = Utf8               Test
  #40 = NameAndType        #24:#25        //  sayhello:(LTest$Human;)V
  #41 = Utf8               java/lang/Object
  #42 = Utf8               Test$Human
  #43 = Utf8               java/lang/System
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Utf8               java/io/PrintStream
  #47 = Utf8               println
  #48 = Utf8               (Ljava/lang/String;)V
{
  public Test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0
        line 13: 4

  public void sayhello(Test$Human);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Human
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 18: 0
        line 19: 8

  public void sayhello(Test$Man);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String Man
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 22: 0
        line 23: 8

  public void sayhello(Test$Woman);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String Woman
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 26: 0
        line 27: 8

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #7                  // class Test$Man
         3: dup
         4: invokespecial #8                  // Method Test$Man."<init>":()V
         7: astore_1
         8: new           #9                  // class Test$Woman
        11: dup
        12: invokespecial #10                 // Method Test$Woman."<init>":()V
        15: astore_2
        16: new           #11                 // class Test
        19: dup
        20: invokespecial #12                 // Method "<init>":()V
        23: astore_3
        24: aload_3
        25: aload_1
        26: invokevirtual #13                 // Method sayhello:(LTest$Human;)V
        29: aload_3
        30: aload_2
        31: invokevirtual #13                 // Method sayhello:(LTest$Human;)V
        34: return
      LineNumberTable:
        line 32: 0
        line 33: 8
        line 35: 16
        line 36: 24
        line 37: 29
        line 38: 34

 

3.1.3、分析

字节码中已经确定了方法的接收者是main和方法的版本是sayhello(Human Human),所以我们在运行代码的时候,会输出Human。

java编译器实际类在重载的时候是通过参数的静态类型来确定使用哪个重载的版本的。所以这里在字节码中,选择了 sayhello(Human Human) 作为调用目标并把这个方法的符号引用写到main方法的几个invokevirtual指令的参数里面。

3.1.4、总结:

  1. 依赖静态类型来定位方法执行的版本的分派动作称为静态分派。
  2. 静态分派的典型应用是方法重载,
  3. 静态分派发生在编译期间,动作是由编译器发出的。

3.1.5、注意:

编译器能确定出方法的重载版本,但在很多的时候,这个版本并不一定是唯一的,jvm会做出妥协,选择并确认一个版本

3.2、动态分派(重写)

动态分派:运行期根据实际类型来判断方法的执行版本的分派过程。

3.2.1、代码

3.2.2、对应的字节码

3.2.3、分析

从字节码来看,这两行代码是一样的。调用了同一个类的同一个方法,,那为什么他们最后的输出却不一样?

这里的原因其实要从invokevirtual的多态查找开始说起,invokevirtual指令运行时的解析过程大概如下:

1. 找到操作数栈的栈顶元素所指向的对象的实际类型,记作C
2. 如果在类型C中找到与描述符和简单名称都相符的方法,则进行访问权限校验。通过则放回这个方法的直接引用,否则返回illegalaccesserror 。
3. 否则,则按照继承关系从下住上依次对C的父类进行步骤2的查找。
4. 如果始终没有找到合适的方法,则跑出AbstractMethodError异常。

由于invokevirtual指令在执行的第1步就对运行的时候的接收者的实际类型进行查找,所以上面两次调用的invokevirtual指令都能成功找到实际类型的sayhello()方法,然后把类方法的符号引用解析到不同的直接引用上面,这也是重写的体现。

​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值