JVM原理(十五):JVM虚拟机静态分配与动态分配

1. 分派

本节讲解的分派调用过程将会揭示多态性特征的一-些最基本的体现,如“重载”和“重写”在Java虚拟机之中是如何实现的。

1.1. 静态分派

案例:

我们先来看一段代码:

Human man=new Man();

我们把上面代码中的“Human"称为变量的“静态类型”( Static Type),或者叫“外观类型”(Apparent Type),后面的“Man”则被称为变量的“实际类型”(Actual Type)或者叫“运行时类型”(Runtime Type)。静态类型和实际类型在程序中都可能会发生变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定编译器在编译程序的时候并不知道一个对象的实际类型是什么。笔者猜想上面这段话读者大概会不太好理解,那不妨通过一段实际例子来解释,譬如有下面的代码:

//实例类型变化
Human human = (new Random()).nextBoolean()?new Man():new Woman();
​
//静态类型变化
sr.sayHello((Man) human)
sr.sayHello((Woman) human)

对象human的实际类型是可变的,编译期间它完全是个“薛定谔的人”,到底是Man还是Woman,必须等到程序运行到这行的时候才能确定。而human的静态类型是Human,也可以在使用时(如say Hello()方法中的强制转型)临时改变这个类型,但这个改变仍是在编译期是可知的,两次sayHello()方法的调用,在编译期完全可以明确转型的是Man还是Woman。

解释清楚了静态类型与实际类型的概念,我们就把话题再转回到代码清单8-6的样例代码中。main()里面的两次say Hello()方法调用,在方法接收者已经确定是对象“sr”的前提下,使用哪个重载版本,就完全取决于传入参数的数量和数据类型。代码中故意定义了两个静态类型相同,而实际类型不同的变量,但虚拟机(或者准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的由于静态类型在编译期可知,所以在编译阶段,Javac编译器就根据参数的静态类型决定了会使用哪个重载版本,因此选择了sayHello(Human)作为调用目标,并把这个方法的符号引用写到main(方法里的两条invokevirtual指令的参数中。

所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表现就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,这点也是为何一些资料选择把它归入“解析”而不是“分派”的原因。

另外还有一点读者可能比较容易混淆:笔者讲述的解析与分派这两者之间的关系并不是二选一的排他关系,它们是在不同层次上去筛选、确定目标方法的过程。例如前面说过静态方法会在编译期确定、在类加载期就进行解析,而静态方法显然也是可以拥有重载版本的,选择重载版本的过程也是通过静态分派完成的。

1.2. 动态分派

动态分派与Java语言多态性的另外一个重要体现——重写(Override)有着很密切的关联。

根据《Java虚拟机规范》invokevirtual指令的运行时解析过程大致分为以下几步:

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

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

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

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

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

既然这种多态性的根源在于虚方法调用指令invokevirtual的执行逻辑,那自然我们得出的结论就只会对方法有效,对字段是无效的,因为字段不使用这条指令。事实上,在Java里面只有虚方法存在,字段永远不可能是虚的,换句话说,字段永远不参与多态,哪个类的方法访问某个名字的字段时,该名字指的就是这个类能看到的那个字段。当子类声明了与父类同名的字段时,虽然在子类的内存中两个字段都会存在,但是子类的字段会遮蔽父类的同名字段

2. 单分派与多分派

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

在main()里调用了两次hardChoice(方法,这两次hardChoice()方法的选择结果在程序输出中已经显示得很清楚了。我们关注的首先是编译阶段中编译器的选择过程,也就是静态分派的过程。这时候选择目标方法的依据有两点:一是静态类型是Father还是Son,二是方法参数是QQ还是360。这次选择结果的最终产物是产生了两条invokevirtual指令,两条指令的参数分别为常量池中指向Fater:hardChoice(360)及Father::hardChoice(QQ)方法的符号引用。因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型

再看看运行阶段中虚拟机的选择,也就是动态分派的过程。在执行“son.hardChoice(new QQ)”这行代码时,更准确地说,是在执行这行代码所对应的invokevirtual指令时,由于编译期已经决定目标方法的签名必须为hardChoice(QQ),虚拟机此时不会关心传递过来的参数“QQ' '到底是“腾讯QQ”还是“奇瑞QQ”,因为这时候参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有该方法的接受者的实际类型是Father还是Son。因为只有一个宗量作为选择依据,所以Java语言的动态分派属于单分派类型

根据上述论证的结果,我们可以总结一句:如今(直至本书编写的Java 12和预览版的Java 13)的Java语言是一门静态多分派、动态单分派的语言。强调“如今的Java语言”是因为这个结论未必会恒久不变,C#在3.0及之前的版本与Java-样是动态单分派语言,但在C#4.0中引入了dynam8ic类型后,就可以很方便地实现动态多分派。JDK 10时Java语法中新出现var关键字,但请读者切勿将其与C#中的dynamic类型混淆,事实上Java的var与C#的var才是相对应的特性,它们与dynamic有着本质的区别:var是在编译时根据声明语句中赋值符右侧的表达式类型来静态地推断类型,这本质是一种语法糖;而dy namic在编译时完全不关心类型是什么,等到运行的时候再进行类型判断。Java语 言中与C#的dynamic类型功能相对接近(只是接近,并不是对等的)的应该是在JDK 9时通过JEP 276引入的jdk. dynalink模块61,使用jdk. dy nalink可以实现在表达式中使用动态类型,Javac编译 器会将这些动态类型的操作翻译为invokedynamic指令的调用点。

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

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

相关文章

iCloud照片库全指南:云端存储与智能管理

iCloud照片库全指南:云端存储与智能管理 在数字化时代,照片和视频成为了我们生活中不可或缺的一部分。随着手机摄像头质量的提升,我们记录生活点滴的方式也越来越丰富。然而,这也带来了一个问题:如何有效管理和存储日…

JavaScript中array.from()

Array.from() 方法在 JavaScript 中用于从一个类似数组或可迭代对象(包括 Set 和 Map,以及字符串、arguments 对象等)中创建一个新的、浅拷贝的数组实例。这个方法的主要用途是将类数组对象(拥有一个 length 属性和若干索引属性的…

alibabacloud学习笔记10

讲解微服务链路追踪系统的作用 讲解什么Sleuth链路追踪系统 注释掉我们的网关过滤器。 注释掉断言。 网关服务,视频服务,订单服务,我们都给这段依赖添加进来。 调用一个请求。 我们可以看到控制台上会有输出。 讲解zipkin介绍和部署实战 访问…

Vite: 实现 no-bundle 开发服务 (2)

概述 基于前文 Vite: 实现 no-bundle 开发服务 (1) 我们基于下面的导图继续实现 no-bundle 构建服务 接下来我们需要完成如下的模块: CSS 编译插件静态资源加载插件模块依赖图开发,并在 transform 中间件中接入HMR 服务端代码开发HMR 客户端代码开发 CSS 编译插件…

virtualbox安装centos及问题

1、安装方式 参考: https://blog.csdn.net/weixin_43888891/article/details/126704497 下载centos: centos7.9 centos官网:https://www.centos.org/ 阿里云镜像站:https://developer.aliyun.com/mirror/ 网易的镜像站&#xff1…

泰国内部安全行动司令部数据泄露

BreachForums 论坛的一名成员宣布发生一起重大数据泄露事件,涉及泰国内部安全行动司令部 (ISOC),该机构被称为泰国皇家武装部队的政治部门。 目前,我们无法准确确认此次泄露的真实性,因为该组织尚未在其网站上发布有关该事件的任…

数据库管理-第217期 Oracle的高可用-02(20240704)

数据库管理217期 2024-07-04 数据库管理-第217期 Oracle的高可用-02(20240704)1 GDS简介2 GDS架构2.1 全局数据服务池2.2 全局数据服务域2.3 全局服务管理2.4 全局数据服务目录2.5 Oracle通知服务 3 GDS简图3.1 负载均衡3.2 只读服务失败转移3.3 多主复制…

项目基础知识

1.JDBC编程和MySQL数据库 数据库的连接(以前写qq项目时的代码) package com.wu.Util; import java.sql.*; public class JDBCUtil {private static JDBCUtil jdbcUtil null;private JDBCUtil() {}public static JDBCUtil getJdbcUtil() {if (jdbcUtil…

WHAT - NextJS 的路由系统和 react-router-dom

不,Next.js 的路由系统并不基于 react-router-dom。Next.js 本身内置了自己的路由系统,并且在其生命周期和工作方式上与 react-router-dom 有所不同。 Next.js 的路由系统 Next.js 提供了一个基于文件系统的路由系统,这意味着每个页面都是一…

剧本杀小程序:助力商家发展,提高游戏体验

近几年,剧本杀游戏已经成为了当下年轻人娱乐的游戏社交方式。与其他游戏相比,剧本杀游戏具有强大的社交性,玩家在游戏中既可以推理玩游戏,也可以与其他玩家交流互动,提高玩家的游戏体验感。 随着互联网的发展&#xf…

java反射-动态调用方法

通过字符串动态创建对象,通过字符串动态使用对象方法 package com.hmdp.service.动态调用方法; import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { String name "javax.swing…

Vue通过Key管理状态

Vue通过Key管理状态 Vue 默认按照“就地更新”的策略来更新,通过 v-for 渲染的元素列表。当数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。为了给 Vue 一个提示…

Oracle 扩展表空间

手动扩展数据文件的大小: 可以通过ALTER DATABASE命令手动增加现有数据文件的大小。例如,要将表空间MY_TABLESPACE的数据文件增加100M,可以使用以下命令: Sql ALTER DATABASE DATAFILE /path/to/datafile.dbf RESIZE 100M; 设置数…

MyBatis(24)MyBatis Generator 是什么,如何使用

MyBatis Generator(MBG)是一个用于自动生成MyBatis的mapper XML文件、mapper接口以及对应实体类的代码生成工具。它能够极大地提高开发效率,避免手动编写大量重复的数据库访问代码。MBG支持通过数据库的表结构生成对应的代码,支持…

VIO(Virtual_Input_Output) IP 使用笔记

VIO(Virtual Input/Output)IP 核,即虚拟输入输出 IP,可以通过调试界面模拟 IO 的变化,这可以在板子没有按键等外设、或外设不足的情况下,来模拟外部输入。然而网上关于 VIO 的教程都说的不是很清楚&#xf…

html高级篇

1.2D转换 转换(transform)你可以简单理解为变形 移动:translate 旋转:rotate 缩放:sCale 移动:translate 1.移动具体值 /* 移动盒子的位置: 定位 盒子的外边距 2d转换移动 */div {width…

eggjs笔记

一、egg.js 1. 什么是egg.js express是基于es5的web开发框架koa1.x 是express原班人员打造的基于es6的web开发框架koa2.x 是express原班人员打造的基于es7的web开发框架egg 是阿里基于koa有约束和规范的企业级web开发框架 2. egg.js的基本使用 2.1 安装 # 初始化 npm init…

【python】OpenCV—Nighttime Low Illumination Image Enhancement

文章目录 1 背景介绍2 代码实现3 原理分析4 效果展示5 附录np.ndindexnumpy.ravelnumpy.argsortcv2.detailEnhancecv2.edgePreservingFilter 1 背景介绍 学习参考来自:OpenCV基础(24)改善夜间图像的照明 源码: 链接&#xff1a…

目标检测中损失函数的精妙作用:精确度与鲁棒性的双重保障

目标检测中损失函数的精妙作用:精确度与鲁棒性的双重保障 目标检测是计算机视觉领域的核心任务之一,它旨在从图像或视频中识别和定位多个对象。在目标检测算法中,损失函数扮演着至关重要的角色,它指导模型学习如何准确地预测边界…

Linux 文件系统以及日志管理

一、inode 与block 1. inode 与block详解 在文件存储硬盘上,硬盘的最小存储单位叫做“扇区”,每个为512字节。 操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取…