聊聊JVM虚方法表和方法调用

这篇具有很好参考价值的文章主要介绍了聊聊JVM虚方法表和方法调用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者:小牛呼噜噜 | https://xiaoniuhululu.com
计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」

大家好,我是呼噜噜,好久没更新文章了,今天我们来填个坑,在之前的一篇文章深挖⾯向对象编程三⼤特性 --封装、继承、多态中
我们遗留了一个问题:当父类引用指向子类对象时,JVM是如何知晓调用的是哪个子类的方法?

动态绑定和静态绑定

我们下文还是用之前文章的例子,简单修改一下:

public class ClassTest {

    static class Animal {
        public void eat(){
            System.out.println("动物吃饭!");
        }
        public void work(){
            System.out.println("动物可以帮助人类干活!");
        }
    }

    static class Cat extends Animal {
        public void eat() {
            System.out.println("吃鱼");
        }
        public void sleep() {
            System.out.println("猫会睡懒觉");
        }
    }

    static class Dog extends Animal {
        public void eat() {
            System.out.println("吃骨头");
        }
    }

    public static void main(String[] args) throws Exception {
        Animal cat=new Cat();
        cat.eat();
        cat.work();
    	  //cat.sleep();//此处编译会报错。
    }

}

当父类引用指向子类对象时,也就是Animal cat=new Cat();这个也叫做向上转型,重写式多态。

这种多态其实是通过动态绑定(dynamic binding)技术来实现,是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。也就是说,只有程序运行起来,你才知道调用的是哪个子类的方法。这种多态可通过函数的重写以及向上转型来实现。

与动态绑定相对应的就是静态绑定,指的是在JVM解析时便能够直接识别目标方法的情况。网上有些文章说,重载和静态绑定直接挂钩,这其实是不完全正确的,笔者举个极端的例子:当某个类中的重载方法被它的子类重写时,那它其实通过了动态绑定。

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

需要注意的是:本文一直在说程序在运行期间发生的事,而方法调用在静态阶段(编译)以声明的静态类型为准,不管符号引用指向的是哪个实例对象。编译成字节码再进入JVM,进行类加载
聊聊JVM虚方法表和方法调用

我们回到刚刚的例子上:
cat.eat();这句的结果打印:吃鱼。程序这块调用我们子类Cat定义的方法,而不是父类的同名方法。
cat.work();这句的结果打印:动物可以帮助人类干活!我们上面Cat类没有定义work方法,但是却使用了父类的方法,这是不是很神奇。其实此处调的是父类的同名方法
cat.sleep();这句 编译器会提示 编译报错。表明:当我们当子类的对象作为父类的引用使用时,只能访问子类中和父类中都有的方法,而无法去访问子类中特有的方法。虽然向上转型是安全的。但是缺点是:一旦向上转型,子类会丢失的子类的扩展方法,其实就是 子类中原本特有的方法就不能再被调用了。所以cat.sleep()这句会编译报错。

由此我们可以发现规律:当发生向上转型,去调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。如果子类没有同名方法,会再次去调父类中的该方法。这种根据对象的实际类型而不是声明类型来选择并调用方法的过程也叫做动态分派(Dynamic Dispatch)
聊聊JVM虚方法表和方法调用
但如果直接这样去查找,会发生循环查找,效率较低,为了解决这个问题,虚方法表 就出现了,也就是动态绑定的底层原理。

虚方法表与虚方法

JVM 虚方法表(Virtual Method Table),也称为vtable,是动态调度用来依次调用虚方法的一种表结构,是一种特殊的索引表

面向对象编程,会频繁地触发动态分派,如果每次动态分配的过程都要重新在类的方法 元数据中搜索合适的目标的方法,就可能影响到执行效率,所以JVM选择了 用空间换取时间的策略来实现动态绑定,为每个类生成一张虚方法表,然后直接通过虚方法表,使用索引来代替循环查找,快速定位目标方法。

在类加载器与双亲委派机制一网打尽一文中,我们知道 类的生命周期一般有如下图有7个阶段,其中阶段1-5为类加载过程,验证、准备、解析统称为连接
聊聊JVM虚方法表和方法调用
虚方法表会在类加载的连接阶段被创建,JVM扫描类的方法信息,识别哪些是虚方法,并在虚方法表中储存其对应的 方法的相关信息以及这些方法在虚拟机内存方法区中的入口地址。这入口地址就是该方法的虚拟方法表的索引,JVM可以通过这个索引地址找到对应的方法。也就是说,每个类的对象都会拥有自己的虚方法表

那什么是虚方法和非虚方法?

非虚方法:如果方法在编译期就确定了具体的调用版本,则这个版本在运行时是不可变的,这样的方法称为非虚方法静态方法。
比如私有方法,final 方法,实例构造器,父类方法都是非虚方法,除了这些以外都是虚方法

当Java中发生向上转型,呈现重写式多态时,如果子类没有重写父类方法,子类并不会复制一份父类的方法到自己的虚方法表中,就会去父类的虚方法表中查找 目标方法

子类的重写的方法和父类中的同名方法在字节码层面方法索引通常来说是一样的,如果在子类找到方法eat(),其索引是0,发现不是要调用的方法后,而是要调用父类的eat(),就会直接去父类方法索引为0的地方查找,这样能进一步提高查找效率。

聊聊JVM虚方法表和方法调用

JVM方法调用的指令

从JVM底层来了解方法调用,我们还需知晓 在JVM中和方法调用有关的指令有5种:

  1. invokeinterface:调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的哪个对象的特定方法。
  2. invokestatic:调用静态方法。
  3. invokespecial: 调用私有实例方法、构造器方法;使用super关键词调用父类的实例方法、构造器;调用所实现接口的default方法
  4. invokevirtual:调用非私有实例方法,也就是虚方法,运行期动态查找的过程。
  5. invokedynamic: 调用动态方法,JDK7新加入的一个虚拟机指令,相比于之前的四条指令,他们的分派逻辑都是固化在JVM内部,而invokedynamic则用于处理新的方法分派:它允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,从而达到动态语言的支持。(Invoke dynamic method)

我们javap来反编译上文例子生成的class文件ClassTest.class:

 public com.zj.ideaprojects.demo.test4.ClassTest();
    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 3: 0

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/zj/ideaprojects/demo/test4/ClassTest$Cat
         3: dup
         4: invokespecial #3                  // Method com/zj/ideaprojects/demo/test4/ClassTest$Cat."<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method com/zj/ideaprojects/demo/test4/ClassTest$Animal.eat:()V
        12: aload_1
        13: invokevirtual #5                  // Method com/zj/ideaprojects/demo/test4/ClassTest$Animal.work:()V
        16: return
      LineNumberTable:
        line 30: 0
        line 31: 8
        line 32: 12
        line 34: 16
    Exceptions:
      throws java.lang.Exception

我们可以发现: Java 中所有非私有实例方法调用都会被编译成 invokevirtual指令,而接口方法调用都会被编译成 invokeinterface 指令。这两种指令,均属于Java 虚拟机中的虚方法调用,会进行函数的动态绑定。

invokevirtual指令在执行时,首先在运行期确定方法接收者的实际类型,并不是把常量池中方法的符号引用(在这里相当于常量池里的方法信息)解析到直接引用上就结束了,而是接着根据方法接收者的实际类型来选择方法版本,这个过程也就是Java多态的本质。

针对于invokeinterface指令来说,虚拟机会建立一个叫做接口方法表的数据结构(interface method table,简称itable),和虚方法表类似。

另外,当我们了解invokespecial指令,invokestatic指令时,可以知晓,父类引用在调用静态方法,私有方法或是接口default方法是不会发生多态,而是直接调用声明类型的方法。

在Java 8中Lambda表达式和默认方法时,底层会生成和使用invokedynamic,很有意思的一个指令,本文就不详细介绍该指令了,以后有机会再讲讲。

小结

小结一下,本文主要讲解了方法调用在Java虚拟机的实现方式,以及虚方法表在 JVM 方法调用中充当了一个中介的角色,使得 JVM 能够实现多态性和动态分派。最后带大家了解一下JVM常见的方法调用的指令,Java可不仅仅只有CRUD哦


参考资料:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.2

《Java虚拟机规范》

《深入理解Java虚拟机:JVM高级特性与最佳实践第3版》


全文完,感谢您的阅读,如果我的文章对你有所帮助的话,还请点个免费的,你的支持会激励我输出更高质量的文章,感谢!

原文镜像:聊聊JVM虚方法表和方法调用

计算机内功、源码解析、科技故事、项目实战、面试八股等更多硬核文章,首发于公众号「小牛呼噜噜」,我们下期再见!

聊聊JVM虚方法表和方法调用文章来源地址https://www.toymoban.com/news/detail-517667.html

到了这里,关于聊聊JVM虚方法表和方法调用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 聊聊分布式架构02——Http到Https

    目录 HTTP通信协议 请求报文 响应报文 持久连接 状态管理 HTTPS通信协议 安全的HTTPS HTTP到HTTPS的演变 对称加密 非对称加密 混合加密机制 证书机构 SSL到底是什么 HTTPS是身披SSL外壳的HTTP HTTP通信协议 一次HTTP请求的通信流程:客户端浏览器通过域名访问网页资源,由DNS解析得到

    2024年02月07日
    浏览(27)
  • 聊聊不同集群的微服务如何通过feign调用

    之前业务部门的某项目微服务调用关系如下图 后因业务改造需要,该项目需要将服务A部署到另外一个集群,但服务A仍然需要能调用到服务B,调用关系如下图 之前调用方式是负责服务B的开发团队提供相应的feign客户端包给到服务A开发团队,服务A开发团队直接将客户端包引入

    2024年02月13日
    浏览(27)
  • 聊聊ChatGLM3多用户并发API调用的问题

    转载请备注出处:https://www.cnblogs.com/zhiyong-ITNote 目前在公司内部4张A10的GPU服务器上部署了ChatGLM3开源模型;然后部署了官方默认的web_demo、api_demo两种模式;重新设计了前端,支持H5和安卓两个客户端调用。但却发现了不能并发访问的问题。 在安卓与H5同时调用ChatGLM的API接口(

    2024年04月09日
    浏览(17)
  • 高频算法题冒险之旅精讲(一)之LeetCode小牛试刀五道题

    📢 导读: 本篇博文是LeetCode算法题讲解篇,对高频算法题进行详细而深入的讲解,解题语言选择的是Java。 更多算法专栏如下: ⛳️ 排序算法 ⛳️ 分治法 ⛳️ LeetCode高频算法题讲解 ⛳️ 数据结构 前言: 本次算法冒险之旅将围绕LeetCode上面的算法面试题汇总进行讲解,该

    2024年02月02日
    浏览(16)
  • html表和连接css的方法

    CSS叫层叠样式表,用来美化HTML, 也可以配合脚本动态的改变样式 提供代码复用, 与HTML代码分离,方便后期维护 1、CSS样式与HTML标签在一起 在标签内,引入style 2、css样式与html标签分离,但不出,使用 给标签id,css中用(#id名字)做连接 3、css文件和html文件分离 1.得有html和css文件

    2024年02月08日
    浏览(18)
  • [经验] 做完腺样体手术打呼噜很严重怎么办 #媒体#笔记#经验分享

    做完腺样体手术打呼噜很严重怎么办 1、打呼噜很严重怎么办 打呼噜是一种常见的睡眠障碍,不仅让睡眠质量变得很糟糕,也会影响室友或家人的睡眠质量。幸运的是,有许多方法可以减少打呼噜的发生率,从而让睡眠变得更好。 保持良好的睡眠姿势非常重要。睡觉时,枕头

    2024年02月20日
    浏览(25)
  • Mybatis-plus 两种分页方法(分单表和联表)

    准备1:引入PageHelper依赖 准备2:新建表格分页数据对象 准备3:分页数据类 准备4:分页工具类 准备5:响应请求分页数据的方法 正式使用 1、如果只是单表查询,可以用QueryWrapper controller层 在service层新增接口以及实现类的方法 2、或者自定义SQL要多表查询,可以自己写方法联

    2024年02月05日
    浏览(22)
  • java https请求,https请求如何调用

    安全超文本传输协议, HTTPS以保密为目标研发, 简单讲HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、 身份认证的网络协议, 其安全基础是SSL协议, 因此加密的详细内容请看SSL。 全称Hypertext Transfer Protocol overSecure Socket Layer。句法类同http:体系。 用于安全的HTTP数据传输。

    2024年02月02日
    浏览(19)
  • 新书发布!《数字化时代研发效能跃升方法与实践》作者序丨IDCF

    作者团队: 姚冬、王立杰、徐磊、许舟平 为什么会有本书 只是因为在人群之中,多看了你一眼。这一切,都要从IDCF的发端讲起  缘起IDCF 时间如白马过隙,我们成立IDCF一晃已有四年,而距离上一本书《敏捷无敌之DevOps时代》的出版也两年有余。 IDCF缘起于2019年5月的一次聚

    2024年02月16日
    浏览(24)
  • 【Java】常见面试题:HTTP/HTTPS、Servlet、Cookie、Linux和JVM

    努力经营当下 直至未来明朗! “抓包工具”:是个特殊的软件,相当于一个“代理程序”,浏览器给服务器的请求就会经过这个代理程序(响应也是经过该代理程序的),进一步的就能分析出请求和响应的结果如何。 fiddler可以抓取http请求,也可以开启抓取https请求。 HTTP

    2024年02月12日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包