• JVM(六)如何执行方法调用


    重写和重载

    重载指的是方法名相同而参数类型不相同的方法之间的关系,重写指的是方法名相同而且参数列表也相同的方法之间的关系 。

    public class OneOverride {
    
        //=========================
        // 这两个方法构成重载
    
        public void show(){
    
        }
    
        public void show(String str) {
    
        }
    
        //===============================
    
    }
    
    
    /**
     * 重写父类方法
     */
    public class OneOverriderChilden extends  OneOverride{
    	
        public void show(String str) {
    
        }
    }
    
    

    java 虚拟机识别方法的关键在于类名,方法名以及方法描述符(method descriptor),方法描述符,它是由方法的参数类型以及返回类型所构成。

    方法调用

    Java中的方法调用分为两大类:

    1、解析调用(Resolution): 在类加载的解析阶段,会把其中的一部分符号引用转化为直接引用。 前提是:方法在程序运行之前,就有一个可确定的调用版本,且该版本在运行期不可变。即“编译期可知,运行期不变”,符合这个要求的主要包括静态方法私有方法两大类,前者与类型直接关联,后者外部无法调用,因此无法通过继承重写。

    2、分派调用(Dispatch):又分为 “静态分派” “动态分派” “多分派” “单分派”。在运行期间才能确定调用方法的版本。

    解析调用

    jvm 字节码调用指令

    jvm 提供了5条调用方法的字节码指令,分别是 :

    • invokestatic: 调用静态方法
    • invokespecial: 调用实例构造器方法、私有方法和父类方法
    • invokevirtual:调用所有的虚方法
    • invokeinterface:调用接口方法,会在运行时确定一个实现此接口的对象
    • invokedynamic: 先在运行时动态解析出调用点限定符所引用的方法,然后再执行

    其中 invokestatic 和 invokespecial 在类加载阶段会把方法的符号引用解析成直接引用(内存地址入口),这类方法也称为非虚方法。

    注意的是: final方法虽然是用invokevirtual来调用的,但是因为它无法被覆盖,是唯一的,不需动态解析的,所以它也是非虚方法。

    来看个例子

    public class StaticResolution {
        public static  void sayHello(){
            System.out.println("hello world");
        }
        public static void main(String[] args) {
            StaticResolution.sayHello();
        }
    }
    
    

    这里调用了静态方法,那么使用 javap -v XX应该会使用 invokestatic

    [root@iZm5e7bivgszquxjh18i39Z jvm测试]# javap -v StaticResolution
    Classfile /home/jvm测试/StaticResolution.class
      Last modified Mar 4, 2020; size 504 bytes
      MD5 checksum f2bbab54fb03714e2332b782be397bfb
      Compiled from "StaticResolution.java"
    public class StaticResolution
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#17         // java/lang/Object."<init>":()V
       #2 = Fieldref           #18.#19        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #20            // hello world
       #4 = Methodref          #21.#22        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = Methodref          #6.#23         // StaticResolution.sayHello:()V
       #6 = Class              #24            // StaticResolution
       #7 = Class              #25            // java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               sayHello
      #13 = Utf8               main
      #14 = Utf8               ([Ljava/lang/String;)V
      #15 = Utf8               SourceFile
      #16 = Utf8               StaticResolution.java
      #17 = NameAndType        #8:#9          // "<init>":()V
      #18 = Class              #26            // java/lang/System
      #19 = NameAndType        #27:#28        // out:Ljava/io/PrintStream;
      #20 = Utf8               hello world
      #21 = Class              #29            // java/io/PrintStream
      #22 = NameAndType        #30:#31        // println:(Ljava/lang/String;)V
      #23 = NameAndType        #12:#9         // sayHello:()V
      #24 = Utf8               StaticResolution
      #25 = Utf8               java/lang/Object
      #26 = Utf8               java/lang/System
      #27 = Utf8               out
      #28 = Utf8               Ljava/io/PrintStream;
      #29 = Utf8               java/io/PrintStream
      #30 = Utf8               println
      #31 = Utf8               (Ljava/lang/String;)V
    {
      public StaticResolution();
        descriptor: ()V
        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 1: 0
    
      public static void sayHello();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #3                  // String hello world
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 4: 0
            line 5: 8
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=0, locals=1, args_size=1
             0: invokestatic  #5                  // Method sayHello:()V
             3: return
          LineNumberTable:
            line 8: 0
            line 9: 3
    }
    SourceFile: "StaticResolution.java"
    
    

    静态分派

    在讲静态分派之前我们需要知道静态类型和动态类型,例如有以下程序 :

    public class StaticDispatch {
        static abstract class Human{}
        static class Man extends  Human{}
        static class Woman extends  Human{}
        
        public void sayHello(Human guy){
            System.out.println("Hello human");
        }
    
        public void sayHello(Man guy){
            System.out.println("Hello man");
        }
    
        public void sayHello(Woman guy){
            System.out.println("Hello woman");
        }
    
        public static void main(String[] args) {
            Human man = new Man();
            Human woman = new Woman();
            StaticDispatch sr = new StaticDispatch();
            sr.sayHello(man);
            sr.sayHello(woman);
        }
    }
    
    输出 : 
    Hello human
    Hello human
    
    

    上面的

    Human man = new Man();
    

    这里 “Human”是 man变量的 静态类型 (Static Type) 或者叫 外观类型(Apparent Type)而后面的 “Man” 则是 man 变量的 实际类型(Actual Type)。静态类型都实际类型在程序中都可以发生变化,** 区别在于静态类型的变化仅仅是在使用时发生,而其本身的静态类型并不发生改变。** 什么意思呢?就是 man 这个对象在被传作参数还是调用方法的时候,我们依然为会认为它是“Human”只有使用的时候它才是“Man”。

    重载与静态分配

    有三个关键点需要知道 :

    • 静态类型在编译期可知,而动态类型只有实际运行时能够获知。
    • 虚拟机是通过参数静态类型作为重载的判定依据
    • 静态分派发生在编译阶段 但是重载有时候也会选择困难--我应该选择哪个重载方法,例如 :
    public class Overload {
        public static void sayHello(Object obj){
            System.out.println("Hello object");
        }
        public static void sayHello(int arg){
            System.out.println("Hello int");
        }
        public static void sayHello(long arg){
            System.out.println("Hello long");
        }
        public static void sayHello(Character arg){
            System.out.println("Hello character");
        }
    
        public static void sayHello(char ...arg){
            System.out.println("Hello char ...");
        }
    
        public static void sayHello(Serializable arg){
            System.out.println("Hello Serializable ");
        }
    
        public static void main(String[] args) {
            sayHello('a');
        }
    }
    
    输出 : 
    Hello int
    
    

    重载的规则:

    1. 自身类型匹配
    2. 是否是基本类型,是,考虑自动装拆箱
    3. 形参的继承关系与重载方法是否匹配
    4. 变长参数匹配

    另外以下也是静态分配 :

    public class ResolutionAndDispatch{
        static void sayHello(int arg){
            System.out.println("Hello int");
        }
        static void sayHello(char arg){
            System.out.println("Hello char");
        }
        public static void main(String[] args){
            ResolutionAndDispatch.sayHello('a’);
        }
    }
    
    

    分派调用

    分派调用揭示了OOP多态性的一些最基本的体现。“重载”和“重写”,就是其中之一。 如下例子 :

    public class DynamicDispatch {
        static abstract class Human{
            protected abstract void sayHello();
        }
        static class Man extends Human{
            @Override
            protected void sayHello() {
                System.out.println("man say hello");
            }
        }
    
        static class Woman extends Human{
            @Override
            protected void sayHello() {
                System.out.println("woman say hello");
            }
        }
    
        public static void main(String[] args) {
            Human man = new Man();
            Human woman = new Woman();
            man.sayHello();
            woman.sayHello();
            man = new Woman();
            man.sayHello();
        }
    }
    
    

    可以看到子类重写了父类的方法。

    [root@iZm5e7bivgszquxjh18i39Z jvm测试]# javap -v  DynamicDispatch
    Classfile /home/jvm测试/DynamicDispatch.class
      Last modified Mar 4, 2020; size 514 bytes
      MD5 checksum 7c19cd382f0b914eac869cb42608314f
      Compiled from "DynamicDispatch.java"
    public class DynamicDispatch
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#22         // java/lang/Object."<init>":()V
       #2 = Class              #23            // DynamicDispatch$Man
       #3 = Methodref          #2.#22         // DynamicDispatch$Man."<init>":()V
       #4 = Class              #24            // DynamicDispatch$Woman
       #5 = Methodref          #4.#22         // DynamicDispatch$Woman."<init>":()V
       #6 = Methodref          #12.#25        // DynamicDispatch$Human.sayHello:()V
       #7 = Class              #26            // DynamicDispatch
       #8 = Class              #27            // java/lang/Object
       #9 = Utf8               Woman
      #10 = Utf8               InnerClasses
      #11 = Utf8               Man
      #12 = Class              #28            // DynamicDispatch$Human
      #13 = Utf8               Human
      #14 = Utf8               <init>
      #15 = Utf8               ()V
      #16 = Utf8               Code
      #17 = Utf8               LineNumberTable
      #18 = Utf8               main
      #19 = Utf8               ([Ljava/lang/String;)V
      #20 = Utf8               SourceFile
      #21 = Utf8               DynamicDispatch.java
      #22 = NameAndType        #14:#15        // "<init>":()V
      #23 = Utf8               DynamicDispatch$Man
      #24 = Utf8               DynamicDispatch$Woman
      #25 = NameAndType        #29:#15        // sayHello:()V
      #26 = Utf8               DynamicDispatch
      #27 = Utf8               java/lang/Object
      #28 = Utf8               DynamicDispatch$Human
      #29 = Utf8               sayHello
    {
      public DynamicDispatch();
        descriptor: ()V
        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 1: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: new           #2                  // class DynamicDispatch$Man
             3: dup
             4: invokespecial #3                  // Method DynamicDispatch$Man."<init>":()V
             7: astore_1
             8: new           #4                  // class DynamicDispatch$Woman
            11: dup
            12: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":()V
            15: astore_2
            16: aload_1
            17: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
            20: aload_2
            21: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
            24: new           #4                  // class DynamicDispatch$Woman
            27: dup
            28: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":()V
            31: astore_1
            32: aload_1
            33: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
            36: return
          LineNumberTable:
            line 20: 0
            line 21: 8
            line 22: 16
            line 23: 20
            line 24: 24
            line 25: 32
            line 26: 36
    }
    SourceFile: "DynamicDispatch.java"
    InnerClasses:
         static #9= #4 of #7; //Woman=class DynamicDispatch$Woman of class DynamicDispatch
         static #11= #2 of #7; //Man=class DynamicDispatch$Man of class DynamicDispatch
         static abstract #13= #12 of #7; //Human=class DynamicDispatch$Human of class DynamicDispatch
    
    

    0~15 在做准备动作 我们看到调用了两次 invokespecial 是调用了实例构造器 构造了man 和woman两个实例,并且把他们的引用放在1、2个局部变量表Slot中接下来的16~21,16和20两句aload_1和aload_2 把创建的对象的引用压到栈顶,这两个对象是将要执行的方法sayHello()的执行者,称作接受者(Receiver) 17和21两句的方法调用指令 和参数 都是一样的,但是最终执行的目标方法不同,原因就是invokevirtual指令的多态查找

    虚方法调用

    java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用都会被编译成 invokeinterface 指令,这均属于java虚拟机中的需方法调用。 **java 虚拟机采取了一种空间换时间的策略来实现动态绑定。**它为每个类生成一个方法表,用以快速定位目标方法。

    方法表

    方法表满足两个特性 :

    • 子类表中包含父类表中的所有方法
    • 子类方法在方法表中的索引值,与它所重写的父类方法的索引值相同

    在执行过程中,java虚拟机将获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法。这个过程便是动态绑定。 思考一下假如我们如果不使用方法表,我们就需要先去收集然后再查找目标方法了,但是即使使用了方法表还有没优化的空间呢?即时编译(JIT)还拥有另外两种性能更好的优化手段 : 内联缓存方法内联

    内联缓存

    它能够缓存虚方法中调用者的动态类型,以及该类型对应的目标方法。 在之后的执行过程中,如果碰到已缓存的类型,直接在缓存中找到对应的目标方法,没有找到,那么就会去方法表中寻找。

    方法内联

    后续讲解

    补充

    查看汇编后的java class

    javap -v xxx
    

    参考资料

    • https://tobiaslee.top/2017/02/14/Override-and-Overload/
    • 《深入JVM》课程
  • 相关阅读:
    hdu 2211(约瑟夫环问题)
    hdu 3605(二分图的多重匹配 | 网络流)
    hdu 3360(经典二分匹配)
    hdu 2255(KM)
    ajax无翻页刷新简单实例2
    在Updatepanel中使用Response.Redirect
    asp.net 使用UpdatePanel 返回服务器处理后弹出对话框
    再记一个SQL分页存储过程
    DIV中图片垂直居中
    在Repeter中用RadioButton生成单选按钮组的实现
  • 原文地址:https://www.cnblogs.com/Benjious/p/12416249.html
Copyright © 2020-2023  润新知