【java编程】深入理解Java虚拟机:方法调用

news/2024/11/7 1:09:20/文章来源:https://www.cnblogs.com/o-O-oO/p/18531355

原创 java秃兔

今天来我们来说一说执行引擎中的方法调用。方法调用并不是指方法中的代码被执行,方法调用阶段唯一的任务就是确定被调用方法的版本,暂时还未涉及方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍、最频繁的操作之一。

解析

所有方法调用的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能够成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。也就是说,调用目标在程序代码写好、编译器进行编译那一刻就已经确定下来。这类方法的调用被称为解析(Resolution)。
在Java中符合“编译期可知,运行期不可变”要求的方法主要有静态方法和私有方法两大类,静态方法与类型直接关联,而私有方法在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写出其他版本,因此它们都适合在类加载阶段进行解析。
调用不同类型的方法,字节码指令集里设计了不同的指令。
在Java虚拟机支持以下5条方法调用字节码指令,分别是:

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

invokedynamic指令的分派逻辑是由用户设定的引导方法来决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本, Java语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类方法4种,再加上被final修饰的方法(尽管它使用invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用。这些方法统称为“非虚方法”(Non-Virtual Method),与之相反,其他方法就被称为“虚方法”(Virtual Method)。

分派

解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为明确的直接引用,不必延迟到运行期再去完成。而另一种主要的方法调用形式:分派 (Dispatch)调用可能是静态的也可能是动态的,按照分派依据的宗量数可分为单分派和多分派。这两类分派方式两两组合就构成了静态单分派、静态多分派、动态单分派、动态多分派4种分派组合情况,下面我们来看看虚拟机中的方法分派是如何进行的。

静态分派

所有依赖静态类型来决定方法执行版本的分派动作都称为静态分派。静态分派的最典型应用表现就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。
值得注意的是Javac编译器虽然能确定出方法的重载版本,但在很多情况下这个重载版本并不是“唯 一”的,往往只能确定一个“相对更合适的”版本。这种模糊的结论在由0和1构成的计算机世界中算是个比较稀罕的事件,产生这种模糊结论的主要原因是字面量天生的模糊性,它不需要定义,所以字面量就没有显式的静态类型,它的静态类型只能通过语言、语法的规则去理解和推断。

动态分派

Java语言里动态分派的实现与Java语言中的重写(Override)有着很密切的关联。要想了解动态分配,就要从invokevirtual指令本身入手。invokevirtual指令的运行时解析过程大致分为以下几步:

1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。

2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。

3)否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。

4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。

正是因为invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以方法调用时invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接收者的实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

单分派与多分派

方法的接收者与方法的参数统称为方法的宗量。根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种。单分派是根据一个宗量对 目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。

在方法调用的编译阶段中编译器的选择过程,也就是静态分派的过程时。选择目标方法的依据有两点:一是静态类型,二是方法参数。选择结果的最终产物是产生了两条invokevirtual指令。因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。

在方法调用的运行阶段中虚拟机的选择,也就是动态分派的过程中。在执行代码所对应的invokevirtual指令时,由于编译期已经决定目标方法的签名,虚拟机此时不会关心传递过来的参数是什么,因为这时候参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚 拟机选择的因素只有该方法的接受者的实际类型。因为只有一个宗量作为选择依据, 所以Java语言的动态分派属于单分派类型。

虚拟机动态分派的实现

前面介绍的分派过程,已经解决了虚拟机在分派中“会做什么”这个问题。但如果问Java虚拟机“具体如何做到”的,答案则可能因各种虚拟机的实现不同会有些差别。

动态分派是执行非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在接收者类型的方法元数据中搜索合适的目标方法,因此,Java虚拟机实现基于执行性能的考虑,真正运行时一般不会如此频繁地去反复搜索类型元数据。面对这种情况,一种基础而且常见的优化手段是为类型在方法区中建立一个虚方法表,使用虚方法表索引来代替元数据查找以提高性能。

虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那子类的虚方法表中的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口。如果子类中重写了这个方法,子类虚方法表中的地址也会被替换为指向子类实现版本的入口地址。

为了程序实现方便,具有相同签名的方法,在父类、子类的虚方法表中都应当具有一样的索引序号,这样当类型变换时,仅需要变更查找的虚方法表,就可以从不同的虚方法表中按索引转换出所需的入口地址。虚方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的虚方法表也一同初始化完毕。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/827962.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【web应用】中间件汇总

原创 boo 学而嘉一、HTTP服务 1.Apache HTTP Server主要用于静态内容服务 最新版本 2.4.592.Nginx主要用于静态内容服务和代理服务器 最新版本1.26.2主要功能: 1.HTTP 服务器(动静分离)nginx + tomcat中,可以将动态资源交给tomcat,而静态资源则交给Nginx,这样可以减轻tom…

c++中的try-catch及throw

C++ 使用 try-catch 语句来捕获和处理异常。try 块包含可能发生错误的代码,而 catch 块则用来捕获并处理错误。 try-catch 语句的基本结构 try {// 可能抛出异常的代码 } catch (exception_type1 e1) {// 处理异常类型 1 } catch (exception_type2 e2) {// 处理异常类型 2 } c…

【windows应用】windows系统怎么查看密钥

原创 L.w IT小农工大家都知道,安装Win10/11系统之后需要密钥才能使用所有的功能,网上提供的激活方法有很多,那我们要怎么知道自己的密钥呢? Windows 产品密钥是由 25 个字符组成的代码,用于激活 Windows。其格式如下所示: 产品密钥:XXXXX-XXXXX-XXXXX-XXXXX-XXXXX 操作过…

【 js编程】原型和原型链

原创 mike Web全栈开发者驿站原型和原型链在 js中起着至关重要的作用。它们使得 js 能够以一种独特的方式实现面向对象编程,虽然与传统的面向对象编程语言有所不同,但却提供了很大的灵活性和强大的功能。然而,原型链也有一些缺点,如属性查找的性能问题、继承的脆弱性以及隐…

全网最适合入门的面向对象编程教程:58 Python字符串与序列化-序列化Web对象的定义与实现

如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML\YAML\JSON格式这种序列化Web对象。这种序列化Web对象容易与其他程序设计语言交互,可读性强,容易被传递给其它系统或客户端。全网最适合入门的面向对象编程教程:58 Python 字符串与序列化-序…

开源低代码平台-Microi吾码-源码本地运行-后端

开源低代码平台-Microi吾码-简介技术框架:.NET8 + Redis + MySql/SqlServer/Oracle + Vue2/3 + Element-UI/Element-Plus 平台始于2014年(基于Avalon.js),2018年使用Vue重构,于2024年10月29日开源 试用地址:https://microi.net Gitee开源地址:https://gitee.com/ITdos/m…

CAPL基础

CAPL基础 1.CAPL如何生效 CAPL通过在Simulation Setup窗口设置CAPL节点,并加载对应的CAPL文件使CAPL生效。 2.Event驱动 CAPL语言的运行逻辑是事件触发,当满足条件时执行对应的代码 如下图所示有启动触发、停止触发、发送报文触发、定时器触发和按键触发等3.报文发送 1.自定义…

开源 - Ideal库 - 常用时间转换扩展方法(一)

分享《开源-Ideal库》系列文章,含公共、文档等库封装,首篇介绍时间转换封装,包括日期时间、时间戳与字符串间转换方法,后续上传至Nuget,测试代码已上传至代码库。从事软件开发这么多年,平时也积累了一些方便自己快速开发的帮助类,一直在想着以什么方式分享出来,因此有了…

excel排序

目录Excel成绩排名的两种方法方法一:先排序,再填排名 Excel成绩排名的两种方法 方法一:先排序,再填排名 具体步骤如下:先左键选中要排序的区域,如下图右键,然后选择排序->自定义排序选择要排序的区域(注意勾选数据包含标题)​ 数据包含标题的意思就是,上面的姓名、…

千锋Linux云计算-文件权限管理

掌握基本权限ugo的命令(chmod、chown),列举2条设置权限命令并解释每个单词含义。 掌握基本权限acl的命令(setfacl、getfacl),列举2条设置权限命令并解释每个单词含义。 了解特殊权限含义(suid、chattr、umask),列举2条设置权限命令并解释每个单词含义。1. 设置传统权…

静态路由规则配置

静态路由配置 本质上通过配置虚拟机实现不同网段之间进行通信 第一步:准备3台虚拟机第一台网卡配置NAT模式;第二台配置两个网卡,分别为配置NAT模式 + LAN区段;第三台配置LAN区段第二步:配置网卡相关信息先查看宿主机(物理机)VMnet8网卡的IP和子网掩码配置网卡相关信息【…

SpringBoot获取文件将要上传的IP地址

SpringBoot获取文件将要上传的IP地址说明: 有的项目会涉及文件上传,比如“更换logo业务”,或者“自定义任务上传脚本等业务”都会涉及上传,而有的项目上传成功后找不到上传地址,所以需要打印IP,方便用户知晓上传的精确地址,下面封装了一个IPv4 工具类(因为是拷贝现成代…