《深入理解Java虚拟机》读书笔记:方法调用

news2024/11/15 23:23:54

  方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍、最频繁的操作,但前面已经讲过,Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用)。这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。

                                   方法调用概要

一、解析

  所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析(Resolution)。

  在Java语言中符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型直接关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写其他版本,因此它们都适合在类加载阶段进行解析。

  与之相对应的是,在Java虚拟机里面提供了5条方法调用字节码指令,分别如下。

  (1)invokestatic:调用静态方法。

  (2)invokespecial:调用实例构造器<init>方法、私有方法和父类方法。

  (3)invokevirtual:调用所有的虚方法。

  (4)invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。

  (5)invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

  只要能被invokestaticinvokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去final方法,后文会提到)。

  Java中的非虚方法除了使用invokestatic、invokespecial调用的方法之外还有一种,就是被final修饰的方法。虽然final方法是使用invokevirtual指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。在Java语言规范中明确说明了final方法是一种非虚方法。

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

二、分派

1、静态分派

  所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。

Human man = new Man();

  我们把上面代码中的“Human”称为变量的静态类型(Static Type),或者叫做的外观类型(Apparent Type),后面的“Man”则称为变量的实际类型(Actual Type),静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定,编译器在编译程序的时候并不知道一个对象的实际类型是什么。

2、动态分派

  我们接下来看一下动态分派的过程,它和多态性的另外一个重要体现——重写(Override)有着很密切的关联。

  invokevirtual指令的运行时解析过程大致分为以下几个步骤:

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

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

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

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

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

3、单分派与多分派

  方法的接收者与方法的参数统称为方法的宗量,这个定义最早应该来源于《Java与模式》一书。根据分派基于多少种宗量,可以将分派划分为单分派多分派两种。

  (1)分派是根据一个宗量对目标方法进行选择;

  (2)多分派则是根据多于一个宗量对目标方法进行选择。

  今天的Java语言是一门静态多分派动态单分派的语言。

4、虚拟机动态分派的实现

  由于动态分派是非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此在虚拟机的实际实现中基于性能的考虑,大部分实现都不会真正地进行如此频繁的搜索。面对这种情况,最常用的“稳定优化”手段就是为类在方法区中建立一个虚方法表(Vritual Method Table,也称为vtable,与此对应的,在invokeinterface执行时也会用到接口方法表——Inteface Method Table,简称itable),使用虚方法表索引来代替元数据查找以提高性能。我们先看看代码清单8-10所对应的虚方法表结构示例,如图8-3所示。

                    图8-3 方法表结构

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

  方法表一般在类加载的连接阶段进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。

5 、动态类型语言支持

  随着JDK 7的发布,字节码指令集终于迎来了第一位新成员——invokedynamic指令。这条新增加的指令是JDK 7实现“动态类型语言”(Dynamically Typed Language)支持而进行的改进之一,也是为JDK 8可以顺利实现Lambda表达式做技术准备。

(1) 动态类型语言

  什么是动态类型语言?动态类型语言的关键特征是它的类型检查的主体过程是在运行期而不是编译期,满足这个特征的语言有很多,常用的包括:APL、Clojure、Erlang、Groovy、JavaScript、Jython、Lisp、Lua、PHP、Prolog、Python、Ruby、Smalltalk和Tcl等。相对的,在编译期就进行类型检查过程的语言(如C++和Java等)就是最常用的静态类型语言。

  在ECMAScript等动态类型语言中,变量obj本身是没有类型的,变量obj的值才具有类型,编译时最多只能确定方法名称、参数、返回值这些信息,而不会去确定方法所在的具体类型(即方法接收者不固定)。“变量无类型而变量值才有类型”这个特点也是动态类型语言的一个重要特征

  静态类型语言在编译期确定类型,最显著的好处是编译器可以提供严谨的类型检查,这样与类型相关的问题能在编码的时候就及时发现,利于稳定性及代码达到更大规模。而动态类型语言在运行期确定类型,这可以为开发人员提供更大的灵活性,某些在静态类型语言中需用大量“臃肿”代码来实现的功能,由动态类型语言来实现可能会更加清晰和简洁,清晰和简洁通常也就意味着开发效率的提升。

(2) JDK 1.7与动态类型

  方法的符号引用在编译时产生,而动态类型语言只有在运行期才能确定接收者类型。这样,在Java虚拟机上实现的动态类型语言就不得不使用其他方式(如编译时留个占位符类型,运行时动态生成字节码实现具体类型到占位符类型的适配)来实现,这样势必让动态类型语言实现的复杂度增加,也可能带来额外的性能或者内存开销。尽管可以利用一些办法(如Call Site Caching)让这些开销尽量变小,但这种底层问题终归是应当在虚拟机层次上去解决才最合适,因此在Java虚拟机层面上提供动态类型的直接支持就成为了Java平台的发展趋势之一,这就是JDK 1.7(JSR-292)中invokedynamic指令以及java.lang.invoke包出现的技术背景。

(3)java.lang.invoke包

  新加入的java.lang.invoke包[插图]就是JSR-292的一个重要组成部分,这个包的主要目的是在之前单纯依靠符号引用来确定调用的目标方法这种方式以外,提供一种新的动态确定目标方法的机制,称为MethodHandle。

  仅站在Java语言的角度来看,MethodHandle的使用方法和效果与Reflection有众多相似之处,不过,它们还是有以下这些区别:

  1、从本质上讲,Reflection和MethodHandle机制都是在模拟方法调用,但Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。在MethodHandles.lookup中的3个方法——findStatic()、findVirtual()、findSpecial()正是为了对应于invokestatic、invokevirtual & invokeinterface和invokespecial这几条字节码指令的执行权限校验行为,而这些底层细节在使用Reflection API时是不需要关心的。

  2、Reflection中的java.lang.reflect.Method对象远比MethodHandle机制中的java.lang. invoke.MethodHandle对象所包含的信息多。前者是方法在Java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含执行权限等的运行期信息。而后者仅仅包含与执行该方法相关的信息。用通俗的话来讲,Reflection是重量级,而MethodHandle是轻量级。

  3、由于MethodHandle是对字节码的方法指令调用的模拟,所以理论上虚拟机在这方面做的各种优化(如方法内联),在MethodHandle上也应当可以采用类似思路去支持(但目前实现还不完善)。而通过反射去调用方法则不行

  MethodHandle与Reflection除了上面列举的区别外,最关键的一点还在于去掉前面讨论施加的前提“仅站在Java语言的角度来看”:Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计成可服务于所有Java虚拟机之上的语言,其中也包括Java语言。

(4)nvokedynamic指令

  在某种程度上,invokedynamic指令与MethodHandle机制的作用是一样的,都是为了解决原有4条“invoke*”指令方法分派规则固化在虚拟机之中的问题,把如何查找目标方法的决定权从虚拟机转嫁到具体用户代码之中,让用户(包含其他语言的设计者)有更高的自由度。而且,它们两者的思路也是可类比的,可以把它们想象成为了达成同一个目的,一个采用上层Java代码和API来实现,另一个用字节码和Class中其他属性、常量来完成。因此,如果理解了前面的MethodHandle例子,那么理解invokedynamic指令也并不困难。

  每一处含有invokedynamic指令的位置都称做“动态调用点”(Dynamic Call Site),这条指令的第一个参数不再是代表方法符号引用的CONSTANT_Methodref_info常量,而是变为JDK 1.7新加入的
CONSTANT_InvokeDynamic_info常量,从这个新常量中可以得到3项信息:引导方法(Bootstrap Method,此方法存放在新增的BootstrapMethods属性中)、方法类型(MethodType)和名称。引导方法是有固定的参数,并且返回值是java.lang.invoke. CallSite对象,这个代表真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并且执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法。

(5)掌控方法分派规则

  invokedynamic指令与前面4条“invoke*”指令的最大差别就是它的分派逻辑不是由虚拟机决定的,而是由程序员决定。在介绍Java虚拟机动态语言支持的最后一个小结中,笔者通过一个简单例子(如代码清单8-14所示),帮助读者理解程序员在可以掌控方法分派规则之后,能做什么以前无法做到的事情。

  在Java程序中,可以通过“super”关键字很方便地调用到父类中的方法,但如果要访问祖类的方法呢?读者在阅读本书下面提供的解决方案之前,不妨自己思考一下,在JDK 1.7之前有没有办法解决这个问题。

  在JDK 1.7之前,使用纯粹的Java语言很难处理这个问题(直接生成字节码就很简单,如使用ASM等字节码工具),原因是在Son类的thinking()方法中无法获取一个实际类型是GrandFather的对象引用,而invokevirtual指令的分派逻辑就是按照方法接收者的实际类型进行分派,这个逻辑是固化在虚拟机中的,程序员无法改变。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx详解 一:编译安装Nginx和Nginx模块

文章目录 1.HTTP 和 Nginx1.1 Socket套接字1.2 HTTP工作机制1.2.1一次http事务1.2.2 资源类型1.2.3提高HTTP连接性能 2. I/O模型2.1 I/O模型相关概念2.2 网络I/O模型2.2.1 **阻塞型** **I/O** 模型&#xff08;blocking IO&#xff09;2.2.2 **非阻塞型** **I/O** **模型** **(…

在React项目是如何捕获错误的?

文章目录 react中的错误介绍解决方案后言 react中的错误介绍 错误在我们日常编写代码是非常常见的 举个例子&#xff0c;在react项目中去编写组件内JavaScript代码错误会导致 React 的内部状态被破坏&#xff0c;导致整个应用崩溃&#xff0c;这是不应该出现的现象 作为一个框架…

Java基础 数据结构一【栈、队列】

什么是数据结构 数据结构是计算机科学中的一个重要概念&#xff0c;用于组织和存储数据以便有效地进行访问、操作和管理。它涉及了如何在计算机内存中组织数据&#xff0c;以便于在不同操作中进行查找、插入、删除等操作 数据结构可以看作是一种数据的组织方式&#xff0c;不…

[maven]关于pom文件中的<relativePath>标签

关于pom文件中的<relativePath>标签 为什么子工程要使用relativePath准确的找到父工程pom.xml.因为本质继承就是pom的继承。父工程pom文件被子工程复用了标签。&#xff08;可以说只要我在父工程定义了标签&#xff0c;子工程就可以没有&#xff0c;因为他继承过来了&…

Kotlin数据结构

数据结构基础 什么是数据结构 在计算机科学中&#xff0c;数据结构&#xff08;Data Structure&#xff09;是计算机中存储、组织数据的方式。数据结构是各种编程语言的基础。 一些使用场景 不同的数据结构适用于不同的应用场景。比如HashMap与ConcurrentHashMap&#xff0…

【太多网工对NAT还存在这4种误解!你是其中一个吗?】

NAT是解决公网地址不够用大家最熟悉的网络技术之一&#xff0c;而NAT最依赖的是NAT translation表项&#xff0c;至于NAT的概念和背景这里不再解释&#xff0c;网络上有很多关于此的类似介绍&#xff0c;自己搜索即可。下面主要是针对大家对NAT的一些误解进行分析。 1 误解一…

leetcode3. 无重复字符的最长子串(滑动窗口 - java)

滑动窗口 无重复字符的最长子串滑动窗口 上期经典 无重复字符的最长子串 难度 - 中等 3. 无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc…

0201hdfs集群部署-hadoop-大数据学习

文章目录 1 前言2 集群规划3 hadoop安装包上传与安装3.1 上传解压 4 hadoop配置5 从节点同步和环境变量配置6 创建用户7 集群启动8 问题集8.1 Invalid URI for NameNode address (check fs.defaultFS): file:/// has no authority. 结语 1 前言 下面我们配置下单namenode节点h…

基于Django的博客管理系统

1、克隆仓库https://gitee.com/lylinux/DjangoBlog.git 若失效&#xff1a;https://gitee.com/usutdzxy/DjangoBlog.git 2、环境安装 pip install -Ur requirements.txt3、修改djangoblog/setting.py 修改数据库配置&#xff0c;其他的步骤就按照官方文档。 DATABASES {def…

无涯教程-机器学习 - Jupyter Notebook函数

Jupyter笔记本基本上为开发基于Python的数据科学应用程序提供了一个交互式计算环境。它们以前称为ipython笔记本。以下是Jupyter笔记本的一些功能,使其成为Python ML生态系统的最佳组件之一- Jupyter笔记本可以逐步排列代码,图像,文本,输出等内容,从而逐步说明分析过程。 它有…

【js案例】滚动效果实现及简单动画函数抽离

目录 &#x1f31f;效果 &#x1f31f;实现思路 &#x1f31f;实现方法 HTML&CSS代码 初始化 滚动效果 完整JS代码 &#x1f31f;抽离动画函数 函数的简单使用 小案例一 小案例二 &#x1f31f;效果 &#x1f31f;实现思路 要实现自动滚动&#xff0c;无非就…

高等数学上册 第十章 重积分 第十一章 曲线积分与曲面积分 知识点总结

重积分 二重积分计算法&#xff1a; 直角坐标下&#xff1a;化为二次积分 { 如果图形是 X Y 型&#xff0c;则都可以&#xff0c;但要考虑哪个计算不定积分方便 如果图形既不是 X 也不是 Y 型&#xff0c;则要拆分 极坐标下&#xff1a; ∬ f ( x , y ) d x d y ∬ f ( ρ cos…

基于适应度相关算法优化的BP神经网络(预测应用) - 附代码

基于适应度相关算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于适应度相关算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.适应度相关优化BP神经网络2.1 BP神经网络参数设置2.2 适应度相关算法应用 4.测试结果&…

Python学习之一 基于交互式解释器的简单Python编程

在很奇葩的Deepin下Miniconda安装之旅 中完成了Deepin系统下的Miniconda安装&#xff0c;在使用Miniconda 中完成了Miniconda的使用。今天&#xff0c;将开始学习Python编程。 (一) 为Python编程学习创建虚拟环境 首先创建虚拟环境&#xff0c;选择Python3.7。 conda create…

用于C++律动运动的中央模式生成器

用于C律动运动的中央模式生成器 一、说明 本篇讲述关于生物模型的神经网络&#xff0c; 中央模式生成器的简单神经网络的一个例子是半中心振荡器&#xff1b;该系统分成两个组成&#xff0c;信号层和物理层。新概念仓本模型&#xff0c;以及龙格库塔法的方程解法&#xff0c;总…

6.跑一下Triton官方教程

1.模型部署 首先拉取官方示例代码 git clone --recursive https://github.com/triton-inference-server/tutorials.git cd tutorials/Conceptual_Guide/Part_1-model_deployment 1.下载文本检测模型 wget https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_dete…

裸露土堆识别算法

裸露土堆识别算法首先利用图像处理技术&#xff0c;提取出图像中的土堆区域。裸露土堆识别算法首通过计算土堆中被绿色防尘网覆盖的比例&#xff0c;判断土堆是否裸露。若超过40%的土堆没有被绿色防尘网覆盖&#xff0c;则视为裸露土堆。当我们谈起计算机视觉时&#xff0c;首先…

React与Vue:两大前端巨头的深度对决

引言 在当今的前端开发领域&#xff0c;React和Vue无疑是两大巨头。它们各自有着独特的历史和哲学&#xff0c;但都为开发者提供了强大的工具来构建高效、响应式的web应用。这篇文章将深入探讨这两个框架的差异&#xff0c;帮助开发者更好地理解它们的优势和劣势。 React与Vu…

Python“牵手”唯品会商品列表数据,关键词搜索唯品会API接口数据,唯品会API接口申请指南

唯品会平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;唯品会API接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问唯品会平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现唯…

2023最新任务悬赏平台源码uniapp+Thinkphp新款悬赏任务地推拉新充场游戏试玩源码众人帮威客兼职任务帮任务发布分销机

新款悬赏任务地推拉新充场游戏试玩源码众人帮威客兼职任务帮任务发布分销机制 后端是&#xff1a;thinkphpFastAdmin 前端是&#xff1a;uniapp 1.优化首页推荐店铺模块如有则会显示此模块没有则隐藏。 2修复首页公告&#xff0c;更改首页公告逻辑。&#xff08;后台添加有公…