JVM(十二):方法调用
JVM(十二):方法调用
在 JVM(七):JVM内存结构 中,我们说到了方法执行在何种内存结构上执行:Java 方法活动在虚拟机栈中的栈帧上,栈帧的具体结构在内存结构中已经详细讲解过了,下面就让我们来看一下 方法是如何调用的。
方法调用
首先,我们要明白一个基础性概念:方法调用并不是方法执行。其只是确定该调用哪一个方法而已(多态的影响,选择方法的不同版本)。并且因为 Java 调用的动态性,有些方法需要在类加载阶段动态解析,这也为 JVM 解析符号引用成直接引用提供了难度。
解析
在 JVM(五):探究类加载过程-上 中,我们说过在类加载过程的解析阶段, JVM 会将符号引用转变为直接引用。但这需要方法在程序真正运行的之前就有一个 可确定 的版本,且这个版本在整个运行期间是不可更改的。这种在类加载阶段就能解析出的方法调用被称为 解析。
解析条件
满足解析条件的方法在 Java 语言中有 静态方法 和 私有方法 两类。前者与类类型直接关联,后者在外部无法被访问。这种特性也使得其不可能通过继承或别的方式来重写其版本,因此适合在解析阶段就进行处理。
在 JVM 内部与之相对应的就是invokestatic
和 invokespecial
两种字节码指令。这两种指令对应的就是 调用静态方法 、调用实例构造器 、 私有方法 、 父类方法。 在执行这四类方法时,在类加载的解析阶段就会将符号引用直接转变为直接引用。
上述的这些方法也被称为 非虚方法 。
满足加载阶段解析的还有一个特殊的方法为 final方法,因为其无法被覆盖,也没有其他版本,因此虽然其是用 invokevirtual
来调用,其也是一种 非虚方法,可以在编译期间就完全确定。
分派
分派则是在程序运行的期间才能确定调用哪个版本的方法。其也揭示了 重载 和 重写 的本质。下面就让我们来看一下其在 JVM 内是如何实现的。
静态分派
// 代码参考自<<深入理解Java虚拟机>>
public class Demo {
static class Human{
public void overWriteDemo(){
System.out.println("human overWriteDemo");
}
}
static class Man extends Human {
@Override
public void overWriteDemo(){
System.out.println("Man overWriteDemo");
}
}
static class Woman extends Human{
@Override
public void overWriteDemo(){
System.out.println("Woman overWriteDemo");
}
}
public void sayHello(Human human){
System.out.println("human");
}
public void sayHello(Man man){
System.out.println("Man");
}
public void sayHello(Woman woman){
System.out.println("Woman");
}
public static void main(String[] args) throws InterruptedException {
Human man = new Man();
Human woman = new Woman();
Demo demo = new Demo();
demo.sayHello(man);
demo.sayHello(woman);
}
}
首先各位读者可以看一下以下代码输出的是什么。
读者可以带着您这边的答案来看下一些概念。上面的 Human 我们称之为变量的 静态类型,或者叫做变量的 外观类型 。后面的 Man 或 Women 我们则称之为变量的 实际类型。
对应这两种类型,变量的静态类型是本身不变的,只有在使用的时候才可以对其进行改变使用,而且最终的静态类型在编译期就可知,如
demo.sayHello((Man)man);
demo.sayHello((Women)man);
这样就可以在使用的时候,使用指定的类型,但其变量本身的属性还是不会更改。
这种语法对应就是 Java 语言中的 重载 语法,虚拟机是通过参数的 静态类型 来确定该调用哪个版本的方法。
现在回到代码输出结果上,第一次代码输出的结果为
Human
Human
第二次代码输出结果为:
Man
Women
动态分派
// 为了节约篇幅,前面的内容就不复制了,只是在调用的时候更改了一下代码,如下所示:
public static void main(String[] args) throws InterruptedException {
Human man = new Man();
Human woman = new Woman();
man.overWriteDemo();
woman.overWriteDemo();
man = new Woman();
man.overWriteDemo();
}
现在读者再来看一下以上这段代码的输出结果是什么,带着心中的答案我们再来看一下 动态分派 的概念。
通过javap
分析其字节码表现,我们发现其调用的是invokevirtual
指令 , 而invokevirtual
在调用的时候,运行时解析步骤为以下方式:
- 找到元素找到的实际类型,记为 C
- 如果在类型 C 中找到签名相符的方法 , 则进行权限校验。 如果通过 , 则返回其对应引用 , 如果没通过 , 则抛出异常;
- 在第二步中 , 如果没找到相符的方法 , 则从 C 从下而上地对其父类进行查找和验证;
- 如果仍旧没有找到 , 则抛出异常 .
通过以上者多层的步骤推进 , 则可以找到实际调用的对象类型,从而决定调用哪个版本的方法。
实现方式
上面我们看到了动态多态的实现逻辑,现在我们来看一下在 JVM 内是如何实现这个过程的.
首先,我们看到该过程需要逐层搜索以找到对应类。但是如果在实际调用过程中,这样逐层的搜索是会影响性能的。因此,面对这种情况,JVM 采用了虚方法表 的形式来实现。在虚方法表中存储了每个方法的实际地址。
方法执行
Java 执行方法分为 解释执行(通过解释器执行) 和 编译执行(通过编译器编译为本地代码执行)两种。
在本文中,我们着重讲一下在解释执行的过程中,JVM 内部是如何实现的。至于编译执行,因为还涉及到 JIT 的优化等概念。放在后面的 编译优化 章节再详细叙说。
基于栈的执行模型
方法执行的过程其实就是在执行 Class 中字节码的指令集部分,而从 # JVM(六):探究类加载过程-下 中,我们已经看到指令集的大部分组成部分,都是零地址指令。那么什么是零地址指令呢,下面给大家举个例子:
iconst_1
iconst_1
iadd
istore_0
以上这段代码就是一段零地址指令,其含义为计算1+1
的结果,具体操作过程为:
- 首先将 1 入栈
- 将另一个 1 入栈
- 将栈顶的两个元素相加,并将计算结果重新入栈
- 将栈顶元素(结果),放入局部变量表中 slot0 位置
基于寄存器的执行模型
而另外还有一种基于寄存器的执行方式,比如上面这段代码如果用寄存器的方式来实现就是如下所示:
mov eax,1
add eax,1
这段指令集的内容就是:
- 将 EAX 寄存器的值设为 1;
- add 指令将eax的值+1,然后结果就保存在eax寄存器上。
执行模型对比
我们很难说清楚两种设计理念哪个更加的好,因为在现如今,两套指令集都在茁壮发展中,因此肯定都有各自的优点,接下来就让我们看一下其各自的优点是什么。
首先既然是基于栈的,那么其最大的优点应该就是 可移植,因为寄存器是与硬件息息相关的,其会受到硬件寄存器的数量等原因限制;其次在栈结构中,大部分指令都是零地址的,那么其第二个优点就是 代码更加的紧凑,而寄存器这样的指令集还需要带上多个参数;最后基于栈的模型,编译器 实现起来也会更加简单,因为所有的操作都是在栈上进行,不需要考虑内存空间分配的问题。
而对寄存器模型来说,其最大的优点就是 执行速度块,因为基于栈来操作的话,同样的操作,基于栈要比基于寄存器来说, 多出大量的指令内容,并且需要频繁的 出栈入栈操作,也就是大量的内存访问操作, 这都会影响指令执行的效率。而这也是所有主流物理机都使用基于寄存器来实现指令集的原因。
总结
在本文中,我们讲述了方法调用和方式执行的过程,与# JVM(七):JVM内存结构 中方法执行时涉及到的内存结构相结合,这三者,使我们详细理清了 Java 程序执行一个方法的全过程,希望读者们以后在写代码中,可以在脑海中清晰地明白这个过程。
文章在公众号「iceWang」第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!
本系列文章主要借鉴自《深入分析 JavaWeb 技术内幕》和《深入理解 Java 虚拟机-JVM 高级特性与最佳实践》。
JVM(十二):方法调用的更多相关文章
- JVM系列-方法调用的原理
JVM系列-方法调用的原理 最近重新看了一些JVM方面的笔记和资料,收获颇丰,尤其解决了长久以来心中关于JVM方法管理的一些疑问.下面介绍一下JVM中有关方法调用的知识. 目的 方法调用,目的是选择方 ...
- java jvm学习笔记十二(访问控制器的栈校验机制)
欢迎装载请说明出处:http://blog.csdn.net/yfqnihao 本节源码:http://download.csdn.net/detail/yfqnihao/4863854 这一节,我们 ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂)
SpringBoot第十二集:度量指标监控与异步调用(2020最新最易懂) Spring Boot Actuator是spring boot项目一个监控模块,提供了很多原生的端点,包含了对应用系统的自 ...
- 04 JVM是如何执行方法调用的(下)
虚方法调用 Java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用会被编译成 invokeinterface 指令.这两种指令,均属于 Java 虚拟机中的虚 ...
- 多态:JVM是如何进行方法调用的
在我们平时的工作学习中写java代码时,如果我们在同一个类中定义了两个方法名和参数类型都相同的方法时,编译器会直接报错给我们.还有在代码运行的时候,如果子类定义了一个与父类完全相同的方法的时候,父类的 ...
- Spring杂谈 | 从桥接方法到JVM方法调用
前言 之所以写这么一篇文章是因为在Spring中,经常会出现下面这种代码 // 判断是否是桥接方法,如果是的话就返回这个方法 BridgeMethodResolver.findBridgedMetho ...
- 深入探究JVM之方法调用及Lambda表达式实现原理
@ 目录 前言 正文 解析 分派 静态分派 动态分派 单分派和多分派 动态分派的实现 Lambda表达式的实现原理 MethodHandle 总结 前言 在最开始讲解JVM内存结构的时候有简单分析过方 ...
- 图解JVM执行引擎之方法调用
一.方法调用 方法调用不同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.Class文件的编译过程中不包括传统编译器中的连接步骤,一 ...
随机推荐
- JavaScript函数及闭包
前面一片文章讲到过一点函数,了解到每声明一个函数就会产生一个作用域.而外面的作用域访问不了里面的作用域(把里面的变量和函数隐藏起来),而里面的可以访问到外面的.对于隐藏变量和函数是一个非常有用的技术. ...
- 洛谷P2319 [HNOI2006]超级英雄 题解
题目链接: https://www.luogu.org/problemnew/show/P2319 分析 每错,这是一道海南不对是河南呀呀呀错了是湖南的省选题. 但是还是可以作为二分图第二题来练手的, ...
- HDU-1576 A/B 基础数论+解题报告
HDU-1576 A/B 基础数论+解题报告 题意 求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973) (我们给定的A必能被B整除,且gcd(B,9973) = 1). 输入 数据 ...
- [ZJOI]2008 生日聚会
显然DP. 将题目转化下: 求由n个0.m个1组成,且满足任意子串0的数量和1的数量差绝对值不超过k的01串数量.n, m≤150,k≤20. 直接做没什么思路,,那我们尽量利用题目的时间和空间限制, ...
- Appium+python自动化(二十三)- 真假美猴王Monkeyrunner与Monkey傻傻的分不清楚(超详解)
简介 看<西游记>第五十七回,说是“六耳猕猴”化作孙悟空的摸样,伤了唐僧,后又和孙悟空大打出手…… 这位假孙悟空,实力不用多说了吧,和真孙悟空一般无二,大战孙悟空,闹到上天入地下海. 在唐 ...
- 二十、Sql Server 保留几位小数的两种做法
问题: 数据库里的 float momey 类型,都会精确到多位小数.但有时候 我们不需要那么精确,例如,只精确到两位有效数字. 解决: 1. 使用 Round() 函数,如 Round(@num,2 ...
- 《PHP从入门到精通(第3版)》目录
一.基础知识 1.初识PHP 2.PHP环境搭建和开发工具 3.PHP语言基础 4.流程控制语句 5.字符串操作 6.正则表达式 7.PHP数组 8.PHP与Web页面交互 9.PHP与JavaScr ...
- 动态规划_Sumsets_POJ-2229
Farmer John commanded his cows to search . Here are the possible sets of numbers that sum to : ) +++ ...
- IDEA创建普通java和web项目教程
1.第一个javaSE项目 01.双击idea运行IDE 02.配置JDK 03.创建项目的workspace .iml文件里面是当前项目的一些配置信息! 相当于web项目中的web.xml文件 04 ...
- IOC容器-Autofac在MVC中实现json方式注入使用
在你阅读时,默认已经了解IOC和autofac的基本用法, 我在最近的我的博客项目中运用了IOC autofac 实现了依赖注入 由于我的项目时asp.net MVC所以我目前向大家展示MVC中如何使 ...