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++中动态多态是通过虚函数来实现的,而虚函数是 ...
随机推荐
- HPUX 11.31 MC/SG恢复丢失的锁盘
有时候由于一些特殊的原因,用户的cluster中的锁盘信息丢失,或者需要更换锁盘,只要执行一个命令就可以了. #cmdisklock reset /dev/vglock:/dev/disk/diskX ...
- 四、oracle 用户管理二
一.使用profile管理用户口令概述:profile是口令限制,资源限制的命令集合,当建立数据库时,oracle会自动建立名称为default的profile.当建立用户没有指定profile选项时 ...
- hosts_allow配置了却不生效
hosts_allow配置了却不生效 配置了两台白名单的机器,一台生效一台不生效,google后的结果都是更新libwrap.so 安装openssh等等..(问题还是没有解决) 经过对比发现,原来 ...
- [zt]手把手教你写对拍程序(PASCAL)
谁适合看这篇文章? ACMERS,OIERS或其它参加算法竞赛或需要算法的人 对操作系统并不太熟悉的人 不会写对拍的人 在网上找不到一个特别详细的对拍样例的人 不嫌弃我写的太低幼的人 前言 在NOIP ...
- Swift-枚举enum理解
//定义一个枚举 //枚举的语法,enum开头,每一行成员的定义使用case关键字开头,一行可以定义多个关键字 enum CompassPoint { case North case South ca ...
- ASP.NET 最全的POST提交数据和接收数据 —— (1) 用url传参方式
//1.对象提交,字典方式 //接口方:public ActionResult GetArry(Car model) public void PostResponse() { HttpWebReque ...
- c++读取文件夹及子文件夹数据
这里有两种情况:读取文件夹下所有嵌套的子文件夹里的所有文件 和 读取文件夹下的指定子文件夹(或所有子文件夹里指定的文件名) <ps,里面和file文件有关的结构体类型和方法在 <io.h ...
- [计算机网络] DNS何时使用TCP协议,何时使用UDP协议
DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类.但很少有人知道DNS分别在什么情况下使用这两种协议. 先简单介绍下TCP与UDP. ...
- Hessian矩阵【转】
http://blog.sina.com.cn/s/blog_7e1ecaf30100wgfw.html 在数学中,海塞矩阵是一个自变量为向量的实值函数的二阶偏导数组成的方块矩阵,一元函数就是二阶导, ...
- 【bzoj3132】上帝造题的七分钟 二维树状数组区间修改区间查询
题目描述 “第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵. 第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作. ...