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

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

一、概念

  • 解析调用:所有方法调用中的目标方法在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. bat 笔记

    cmd删除非空文件夹 rd+空格+/s/q+空格+d:\filedir for语句的基本用法 在批处理文件中: FOR %%variable IN (command1) DO command2 [co ...

  2. python学习笔记:json与字典的转换(dump 和dumps load和loads的区别)

    1. json序列化(字典转成字符串)方法: dumps:无文件操作            dump:序列化+写入文件 2. json反序列化(字符串转成字典)方法: loads:无文件操作     ...

  3. Django框架(二十六)—— Django rest_framework-分页器与版本控制

    目录 分页器与版本控制 一.三种分页器 二.分页器 1.普通分页(PageNumberPagination) 2.偏移分页(LimitOffsetPagination) 3.加密分页(CursorPa ...

  4. python 模拟双色球输出

    编写Python函数:完成一个双色球彩票的模拟生成过程, 其中前六个为红色球,数字范围1-33,不可重复.最后一个为蓝色球 1-16. import random #red_nums是采集红色球的数字 ...

  5. swoole安装异步reids

    /usr/local/php/bin/phpize ./configure --with-php-config=/usr/local/php/bin/php-config --enable-async ...

  6. Spring Boot国际化支持

    本章将讲解如何在Spring Boot和Thymeleaf中做页面模板国际化的支持,根据系统语言环境或者session中的语言来自动读取不同环境中的文字. 国际化自动配置 Spring Boot中已经 ...

  7. 廖雪峰的git学习笔记

    安装完后,每个机器都要自报家门 Config--配置      global--全局参数 配置全局用户名 $git config --global user.name “Your Name” 配置邮箱 ...

  8. Hibernate4教程一:入门介绍

    第一部分:Hibernate入门 Hibernate是什么     Hibernate是一个轻量级的ORM框架     ORM原理(Object Relational Mapping)     ORM ...

  9. Vue--入门篇

    一.v-model和单选按钮(radio) <div id="app"> <input name="sex" value="男&qu ...

  10. 如何取消IDEA的自动删除行尾空格?

    使用IDEA,添加注释的时候敲空格,总是会把行尾空格删除导致代码跑到注释行,很不爽~~ 取消这个不爽的功能:File--Settings--Editor--General--Other--Strip ...