这一节打算从“方法调用”的主题进行分析。

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不设计方法内部的具体运行过程。

一、概念

  • 解析调用:所有方法调用中的目标方法在Class文件里都是一个常量池中的引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用。也就是说,调用目标在程序代码写好、编译器进行编译时就必须能确定下来的方法调用成为解析

小插曲:这里提供5条方法调用字节码指令

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

只要能被invokestatic 和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,他们在类加载的时候就会把符号引用解析为该方法的直接引用,这些方法也可以成为非虚方法。另外,在Java语言规范中明确说明了final方法也是一种非虚方法,虽然final方法是使用invokevirtual指令来调用的,但是无法被覆盖,多态的选择结果也肯定是唯一的。

  • 分派调用
    1. 静态分派(体现为重载)
    2. 动态分派(体现为重写)

二、代码

  1. 重载
    1. 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,guy");
      } public void sayHello(Man guy){
      System.out.println("hello,man");
      }
      public void sayHello(Woman guy){
      System.out.println("hello,girl");
      }
      public static void main(String... args){
      Human man = new Man();
      Human woman = new Woman();
      //Human称为变量的静态类型,后面的Man被称为实际类型。  
      StaticDispatch sr = new StaticDispatch();
      sr.sayHello(man);
      sr.sayHello(woman);
      //静态类型变化(就是在使用时改变静态类型,变量本身的静态类型不会被改变,并且最终的静态类型是在编译器可知的)
      sr.sayHello((Man)man);
      sr.sayHello((Woman)woman);
      }
      }

      输出:

      [root@localhost tmp4]# java StaticDispatch
      hello,guy
      hello,guy
      hello,man
      hello,girl

      分析:(上面的代码需要直接使用javac编译,绕过IDE的语法识别就可以了)在没有发生强制转换的时候,使用的是静态类型。当发生静态类型变化的时候,也就是使用强制转换的时候,虚拟机才会识别实际类型。

    2. 上面代码对应的字节码
      Constant pool:
      #1 = Methodref #16.#34 // java/lang/Object."<init>":()V
      #2 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
      #3 = String #37 // hello,guy
      #4 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V
      #5 = String #40 // hello,man
      #6 = String #41 // hello,girl
      #7 = Class #42 // StaticDispatch$Man
      #8 = Methodref #7.#34 // StaticDispatch$Man."<init>":()V
      #9 = Class #43 // StaticDispatch$Woman
      #10 = Methodref #9.#34 // StaticDispatch$Woman."<init>":()V
      #11 = Class #44 // StaticDispatch
      #12 = Methodref #11.#34 // StaticDispatch."<init>":()V
      #13 = Methodref #11.#45 // StaticDispatch.sayHello:(LStaticDispatch$Human;)V
      #14 = Methodref #11.#46 // StaticDispatch.sayHello:(LStaticDispatch$Man;)V
      #15 = Methodref #11.#47 // StaticDispatch.sayHello:(LStaticDispatch$Woman;)V
      #16 = Class #48 // java/lang/Object
      #17 = Utf8 Woman
      #18 = Utf8 InnerClasses
      #19 = Utf8 Man
      #20 = Class #49 // StaticDispatch$Human
      #21 = Utf8 Human
      #22 = Utf8 <init>
      #23 = Utf8 ()V
      #24 = Utf8 Code
      #25 = Utf8 LineNumberTable
      #26 = Utf8 sayHello
      #27 = Utf8 (LStaticDispatch$Human;)V
      #28 = Utf8 (LStaticDispatch$Man;)V
      #29 = Utf8 (LStaticDispatch$Woman;)V
      #30 = Utf8 main
      #31 = Utf8 ([Ljava/lang/String;)V
      #32 = Utf8 SourceFile
      #33 = Utf8 StaticDispatch.java
      #34 = NameAndType #22:#23 // "<init>":()V
      #35 = Class #50 // java/lang/System
      #36 = NameAndType #51:#52 // out:Ljava/io/PrintStream;
      #37 = Utf8 hello,guy
      #38 = Class #53 // java/io/PrintStream
      #39 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
      #40 = Utf8 hello,man
      #41 = Utf8 hello,girl
      #42 = Utf8 StaticDispatch$Man
      #43 = Utf8 StaticDispatch$Woman
      #44 = Utf8 StaticDispatch
      #45 = NameAndType #26:#27 // sayHello:(LStaticDispatch$Human;)V
      #46 = NameAndType #26:#28 // sayHello:(LStaticDispatch$Man;)V
      #47 = NameAndType #26:#29 // sayHello:(LStaticDispatch$Woman;)V
      #48 = Utf8 java/lang/Object
      #49 = Utf8 StaticDispatch$Human
      #50 = Utf8 java/lang/System
      #51 = Utf8 out
      #52 = Utf8 Ljava/io/PrintStream;
      #53 = Utf8 java/io/PrintStream
      #54 = Utf8 println
      #55 = Utf8 (Ljava/lang/String;)V

      上面是常量池。(也就是#号对上的引用)

      public static void main(java.lang.String...);
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
      Code:
      stack=2, locals=4, args_size=1
      0: new #7 // class StaticDispatch$Man
      3: dup
      4: invokespecial #8 // Method StaticDispatch$Man."<init>":()V
      7: astore_1
      8: new #9 // class StaticDispatch$Woman
      11: dup
      12: invokespecial #10 // Method StaticDispatch$Woman."<init>":()V
      15: astore_2
      16: new #11 // class StaticDispatch
      19: dup
      20: invokespecial #12 // Method "<init>":()V
      23: astore_3
      24: aload_3
      25: aload_1
      26: invokevirtual #13 // Method sayHello:(LStaticDispatch$Human;)V
      29: aload_3
      30: aload_2
      31: invokevirtual #13 // Method sayHello:(LStaticDispatch$Human;)V
      34: aload_3
      35: aload_1
      36: checkcast #7 // class StaticDispatch$Man
      39: invokevirtual #14 // Method sayHello:(LStaticDispatch$Man;)V
      42: aload_3
      43: aload_2
      44: checkcast #9 // class StaticDispatch$Woman
      47: invokevirtual #15 // Method sayHello:(LStaticDispatch$Woman;)V
      50: return
      LineNumberTable:
      line 18: 0
      line 19: 8
      line 21: 16
      line 22: 24
      line 23: 29
      line 24: 34
      line 25: 42
      line 27: 50
      }
      SourceFile: "StaticDispatch.java"
      InnerClasses:
      static #17= #9 of #11; //Woman=class StaticDispatch$Woman of class StaticDispatch
      static #19= #7 of #11; //Man=class StaticDispatch$Man of class StaticDispatch
      static abstract #21= #20 of #11; //Human=class StaticDispatch$Human of class StaticDispatch

      这里的字节码中,可以看到通过invokespecial调用类的构造方法之后,到了26行和31行使用了分派默认调用了静态类型即Human,而39行和47行前使用了强转化之后,invokevirtual调用虚方法就会分派指向实际类型。

  2. 重写
    1. java代码

      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@localhost tmp5]# java DynamicDispatch
      man say hello
      Woman say hello
      Woman say hello
    2. 字节码
      [root@localhost tmp5]# javap -verbose DynamicDispatch
      Classfile /usr/local/asmtools-7.0-build/binaries/lib/tmp5/DynamicDispatch.class
      Last modified Aug 19, 2019; size 514 bytes
      MD5 checksum a9486d1c7dc75a210b82bd18f1782dfa
      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 22: 0
      line 23: 8
      line 25: 16
      line 26: 20
      line 28: 24
      line 29: 32
      line 30: 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
    3. 在这里需要说明一下invokevirtual指令的运行时解析过程大致分为以下几个步骤:
      1.找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
      
      2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
      
      3.否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
      
      4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

《深入理解Java虚拟机》- 重载与重写的更多相关文章

  1. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  2. 深入理解Java虚拟机--中

    深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...

  3. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

  4. 深入理解Java虚拟机(字节码执行引擎)

    深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...

  5. 《深入理解 Java 虚拟机》笔记整理

    正文 一.Java 内存区域与内存溢出异常 1.运行时数据区域 程序计数器:当前线程所执行的字节码的行号指示器.线程私有. Java 虚拟机栈:Java 方法执行的内存模型.线程私有. 本地方法栈:N ...

  6. 深入理解Java虚拟机---学习感悟以及笔记

    一.为什么要学习Java虚拟机?       这里我们使用举例来说明为什么要学习Java虚拟机,其实这个问题就和为什么要学习数据结构和算法是一个道理,工欲善其事,必先利其器.曾经的我经常害怕处理内存溢 ...

  7. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

  8. jvm--深入理解java虚拟机 精华总结(面试)(转)

    深入理解java虚拟机 精华总结(面试)(转) 原文地址:http://www.cnblogs.com/prayers/p/5515245.html 一.运行时数据区域 3 1.1 程序计数器 3 1 ...

  9. 2018.4.23 深入理解java虚拟机(转)

    深入理解java虚拟机 精华总结(面试) 一.运行时数据区域 Java虚拟机管理的内存包括几个运行时数据内存:方法区.虚拟机栈.本地方法栈.堆.程序计数器,其中方法区和堆是由线程共享的数据区,其他几个 ...

  10. 深入理解Java虚拟机:垃圾收集器与内存分配策略

    目录 3.2 对象已死吗 判断一个对象是否可被回收 引用类型 finalize() 回收方法区 3.3. 垃圾收集算法 1.Mark-Sweep(标记-清除)算法 2.Copying(复制)算法 3. ...

随机推荐

  1. 力扣算法——142LinkedListCycleII

    Given a linked list, return the node where the cycle begins. If there is no cycle, return null. To r ...

  2. QT的一些小知识

    记录一下前段时间工作中用到的东西,包括开发工具和一些简单的技巧吧.也许对于大家来说耳熟能详了. 最开始学习QT记得是在Ubuntu12.04下用apt命令行的方式安装了QT4.8.4以及QT Crea ...

  3. 开发效率优化之自动化构建系统Gradle(二)上篇

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将以下两个内容来介绍自动化构建系统Gradle: gra ...

  4. Oracle查询最近执行过的SQL语句

    oracle 查询最近执行过的 SQL语句 select sql_text,last_load_time from v$sql order by last_load_time desc; SELECT ...

  5. Python之列表转字典:setdefault、defaultdict、fromkeys

    setdefault result = {} data = [("p", 1), ("p", 2), ("p", 3), ("h& ...

  6. 使用Condition实现顺序执行

    参考<Java多线程编程核心技术> 使用Condition对象可以对线程执行的业务进行排序规划 具体实现代码 public class Run2 { private static Reen ...

  7. Android 导致OOM的常见原因

    OOM主要有两种原因导致: 1. 加载大图片: 2. 内存泄漏: 一.加载大图片 在Android应用中加载Bitmap的操作是需要特别小心处理的,因为Bitmap会消耗很多内存.比如,Galaxy ...

  8. Shell 变量操作

  9. 每天一个Linux常用命令 cp命令

    Linux cp命令主要用于复制文件或目录 -a:此选项通常在复制目录时使用,它保留链接.文件属性,并复制目录下的所有内容.其作用等于dpR参数组合. -d:复制时保留链接.这里所说的链接相当于Win ...

  10. Java中的杂流(闸总)

    标准输入输出流 System.in: 标准输入流是InputStream的子类对象,字节输入流,只不过是jvm给定的唯一一个从键盘控制条读入的流. public static final InputS ...