JVM之字节码执行引擎
方法调用:
方法调用不同于方法执行,方法调用阶段唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不执行方法内部的具体过程。方法调用有,解析调用,分派调用(有静态分派,动态分派)。
方法解析:
解析调用一定是一个静态的过程,在编译期就完全确定,可以在类加载的解析阶段就把涉及的符号引用转化为直接引用,不会延迟到运行期再去完成。
所有方法调用的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用(前提是,方法在程序真正运行前就可以确定调用的版本,并且这个方法的调用版本在运行期不会改变,也就是说,调用目标在程序代码写好,编译器编译时就能确定下来)。这类方法的调用称为解析。
编译期可知,运行期不变,这样的方法主要有静态方法和私有方法这两大类,前者与类型关联,后者在外部不可被访问,这两种方法各自的特点决定了它们都不能通过继承或别的方式重写其他版本,因此它们都适合在类加载阶段进行解析。
与之对应的是,在Java虚拟机里面提供了5条调用字节码指令,有:
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法,私有方法和父类方法。
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时在确定一个实现此接口的对象。
只要能被invodestatic,invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本(而invokevirtual,invokeinterface指令调用的方法则不行,因为他们2调用的方法需要去找到子类对应的方法(如果有的话),实现该接口的类的方法,这些都是在运行期确定的)。能被invokestatic,invokespecial指令对应的方法有静态方法,私有方法,实例构造器方法,父类方法,它们会在类加载的时候(准确说是在解析阶段)就会把符号引用转化为直接引用,这些方法称为非虚方法,与之对应的称为虚方法(final方法除外,即使它是虚方法,但是因为它不能被重写,可以在类加载的解析阶段就把涉及的符号引用转化为直接引用,不会延迟到运行期再去完成)。
分派之静态分派:
分派调用可能是静态的,也可能是动态的,根据分派的宗量数可分为单分派和多分派。分派调用过程将会揭示多态性特征的一些基本的体现,如重载,重写是怎样实现的。、
变量的类型有静态类型和实际类型之分,所以在加载器无法确定变量的实际类型,也就无法确定变量所调用方法的版本。
静态类型(或者称外观类型,C++中称为编译器类型),实际类型(C++中称为运行期类型)。这两个概念是实现多态的基础。
虚拟机(准确说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的,静态类型是编译期可知的,因为在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本。所有依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派的典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的。另外,编译器虽然能确定出方法的重载版本,但在很多情况在这个版本有多个选择,往往只能确定一个更加合适的。如,基础类型变量,以字符变量为例重载时首选其实际的类型(接触类型没有静态类型这一说),然后是int,long,Character,Serializable,Object.
分派之动态分派:
invokevirtual指令(执行所有的虚方法)的运行时解析过程大致分为以下几个步骤:
1.找到操作数栈顶的第一个元素所指向的对象(调用当前方法的对象)的实际类型,记作C
2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过抛出java.lang.IllegalAccessError异常;
3.否则,按照继承关系从下往上依次对C的父类进行第2步的搜索和校验过程;
4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
这个过程就是Java语言中方法重写的本质,在这个过程中将常量池中的类方法的符号引用转化为直接引用(针对于这些虚方法)。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。
单分派与多分派:
方法的接收者与方法的参数统称为方法的宗量。单分派是根据一个宗量对目标方法进行选择,多分派则是根据多于一个宗量对目标方法进行选择。
对照实例来分析:
public class Dispath
{
static class QQ{} static class _360{} public static class Father {
public void hardChoice(QQ arg){
System.out.println("father choose qq");
} public void hardChoice(_360 arg){
System.out.println("father choose 360");
}
} public static class Son extends Father {
public void hardChoice(QQ arg){
System.out.println("son choose qq");
} public void hardChoice(_360 arg){
System.out.println("son choose 360");
}
} public static void main(String[] args){
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}
对于hardChoice()方法
1.编译器选择方法版本的过程,也就是静态分派的过程。这时选择目标方法的依据有两点:一点是静态类型是Father还是Son,二是方法参数是QQ还是360。这次选择结果的产物是产生了两条invokevirtual指令,两个指令的参数分别为常量池中指向Father.hardChoice(360)及Father.hardChoice(QQ)方法的符号引用。如图:
因为是根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。
2.运行阶段的选择,也就是动态分派的过程。在执行“son.hardChoice(new QQ())”这句代码时,即invokevirtual指令时,由于编译期已经决定目标方法的签名必须是hardChoice(QQ),虚拟机此时不会关心传进来的参数类型了,因为这时的参数的静态类型,实际类型已经对方法的选择不会产生影响了(已经进行了重载,剩下的只有重写了)。唯一产生影响的是方法调用者的实际类型是Father还是Son。因为只有一个宗量作为选择依据,所以动态分派属于单分派类型。
基于栈的字节码解释执行引擎:
许多Java虚拟机的执行引擎在执行Java代码时都有解释执行(通过解释器执行),编译执行(通过即时编译器产生本地代码执行)两种选择。
Java编译器输出的指令流,基本上是基于栈的指令集架构,指令流中的指令大部分是零地址指令,他们依赖操作栈进行工作。与之对应的是基于寄存器的指令集,如x86二地址指令集(主流PC机使用)。
基于栈的指令集优点:可移植,代码想对紧凑(一字节对应一个指令),编译器实现更加简单(不用考虑空间分配问题,所需空间在栈上操作)。
缺点:速度慢(因为指令数量多,内存访问频繁,访问栈属于内存访问)。
基于栈的解释器执行过程跟处理器执行方式相似。
JVM之字节码执行引擎的更多相关文章
- Java之深入JVM(6) - 字节码执行引擎(转)
本文为转载,来自 前面我们不止一次的提到,Java是一种跨平台的语言,为什么可以跨平台,因为我们编译的结果是中间代码—字节码,而不是机器码,那字节码在整个Java平台扮演着什么样的角色的呢?JDK1. ...
- JVM(6) 字节码执行引擎
编译器(javac)将Java源文件(.java文件)编译成Java字节码(.class文件). 类加载器负责加载编译后的字节码,并加载到运行时数据区(Runtime Data Area) 通过类加载 ...
- 一夜搞懂 | JVM 字节码执行引擎
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...
- JVM学习笔记:字节码执行引擎
JVM学习笔记:字节码执行引擎 移步大神贴:http://rednaxelafx.iteye.com/blog/492667
- JVM总结(五):JVM字节码执行引擎
JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...
- 深入理解JVM虚拟机5:虚拟机字节码执行引擎
虚拟机字节码执行引擎 转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...
- JVM基础结构与字节码执行引擎
JVM基础结构 JVM内部结构如下:栈.堆. 栈 JVM中的栈主要是指线程里面的栈,里面有方法栈.native方法栈.PC寄存器等等:每个方法栈是由栈帧组成的:每个栈帧是由局部变量表.操作数栈等组成. ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性
我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...
随机推荐
- RL_Learning
Key Concepts in RL 标签(空格分隔): RL_learning OpenAI Spinning Up原址 states and observations (状态和观测) action ...
- 《剑指Offer》题二十一~题三十
二十一.调整数组顺序使奇数位于偶数前面 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分. 测试用例: 功能测试:输入数组中的奇 ...
- Thunder团队第一周 - Scrum会议3
Scrum会议3 小组名称:Thunder 项目名称:在线考试系统 Scrum Master:杨梓瑞 工作照片: 参会成员: 王航:http://www.cnblogs.com/wangh013/ 李 ...
- 无法启动mysql服务 错误1067:进程意外中止
这个错误在前些周遇到过,没有解决,直接粗暴的卸载重装了,自己用的是wampserver集成环境,重装的后果是mysql里面的一些已有的数据库就没有了,有点小悲剧,不过幸好都是一些测试用的数据库,后面直 ...
- Java package和import语句
Java中的package和import语句 如果你想让其他人访问你的类,你一定要把你写的类放到正确的子目录下. 在Java里,对于位于包中的类是这样管理的: Java编译器把包对应于文件系统的目录管 ...
- OSG学习:自动对齐节点示例
/********************************************************** *Write by FlySky *zzuxp@163.com http://w ...
- php添加扩展 在phpinfo能看到该扩展,但在cli用php -m 却看不到,为什么呢,求指教
1. 没有出现的原因是:执行时添加上php.ini的文件就可以了 $ /usr/local/php/bin/php -c /usr/local/php/etc/php.ini -m | grep ...
- 【python】 requirements使用方法
记得导入导出包的时候要想激活虚拟环境.1.导出requirements方法pip freeze > requirements.txt 2.安装requirements方法pip install ...
- 【hdu4507】吉哥系列故事——恨7不成妻 数位dp
题目描述 求 $[L,R]$ 内满足:数位中不包含7.数位之和不是7的倍数.本身不是7的倍数 的所有数的平方和 mod $10^9+7$ . 输入 输入数据的第一行是case数T(1 <= T ...
- 通过logger命令记录日志
通过logger命令记录日志 logger是一个shell命令接口,可以通过该接口使用Syslog的系统日志模块,还可以从命令行直接向系统日志文件写入一行信息. ------------------- ...