方法调用:

方法调用不同于方法执行,方法调用阶段唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不执行方法内部的具体过程。方法调用有,解析调用,分派调用(有静态分派,动态分派)。

方法解析:

解析调用一定是一个静态的过程,在编译期就完全确定,可以在类加载的解析阶段就把涉及的符号引用转化为直接引用,不会延迟到运行期再去完成。

所有方法调用的目标方法在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之字节码执行引擎的更多相关文章

  1. Java之深入JVM(6) - 字节码执行引擎(转)

    本文为转载,来自 前面我们不止一次的提到,Java是一种跨平台的语言,为什么可以跨平台,因为我们编译的结果是中间代码—字节码,而不是机器码,那字节码在整个Java平台扮演着什么样的角色的呢?JDK1. ...

  2. JVM(6) 字节码执行引擎

    编译器(javac)将Java源文件(.java文件)编译成Java字节码(.class文件). 类加载器负责加载编译后的字节码,并加载到运行时数据区(Runtime Data Area) 通过类加载 ...

  3. 一夜搞懂 | JVM 字节码执行引擎

    前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...

  4. JVM学习笔记:字节码执行引擎

    JVM学习笔记:字节码执行引擎 移步大神贴:http://rednaxelafx.iteye.com/blog/492667  

  5. JVM总结(五):JVM字节码执行引擎

    JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...

  6. 深入理解JVM虚拟机5:虚拟机字节码执行引擎

    虚拟机字节码执行引擎   转自https://juejin.im/post/5abc97ff518825556a727e66 所谓的「虚拟机字节码执行引擎」其实就是 JVM 根据 Class 文件中给 ...

  7. JVM基础结构与字节码执行引擎

    JVM基础结构 JVM内部结构如下:栈.堆. 栈 JVM中的栈主要是指线程里面的栈,里面有方法栈.native方法栈.PC寄存器等等:每个方法栈是由栈帧组成的:每个栈帧是由局部变量表.操作数栈等组成. ...

  8. 深入理解java虚拟机(5)---字节码执行引擎

    字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...

  9. 【java虚拟机系列】从java虚拟机字节码执行引擎的执行过程来彻底理解java的多态性

    我们知道面向对象语言的三大特点之一就是多态性,而java作为一种面向对象的语言,自然也满足多态性,我们也知道java中的多态包括重载与重写,我们也知道在C++中动态多态是通过虚函数来实现的,而虚函数是 ...

随机推荐

  1. 开源自动驾驶仿真平台 AirSim (1) - Unreal Engine

    AirSim 官方Github: https://github.com/Microsoft/AirSim AirSim 是微软的开源自动驾驶仿真平台(其实它还能做很多事情,这里主要用于自动驾驶仿真研究 ...

  2. leetcode个人题解——#19 Remove Nth Node From End of List

    思路:设置两个指针,其中第二个指针比第一个延迟n个元素,这样,当第二个指针遍历到指针尾部时,对第一个指针进行删除操作. 当然,这题要注意一些边界值,比如输入[1,2] n=2时如果按照思路走会指向未分 ...

  3. geth账户密码

    xiaocong geth账户密码 123 {d6abe909013d8da914ae2a08c9b58e7b76601b39} 账户密码 123456 0x4A7F15104F54dB3214D2F ...

  4. 软工Hello World!团队第二周博客汇总

    2017.10.20-2017.10.26 Scrum会议: 第一天:http://www.cnblogs.com/liusx0303/p/7704482.html 第二天:http://www.cn ...

  5. 2017-2018-2 20172323 『Java程序设计』课程 结对编程练习_四则运算 2

    相关过程截图 关键代码解释 将运算式分开的代码 String[] result = num.split("\\s"); 将输入的num以空格为间隔符号分开,将每一个间隔开的字符存入 ...

  6. 课堂练习之找数字0-N中“1”出现的次数

    一.题目与要求 题目:给定一个十进制的正整数,写下从1开始,到N的所有整数,然后数一下其中出现“1”的个数. 要求:1.写一个函数 f(N) ,返回1 到 N 之间出现的“1”的个数.例如 f(12) ...

  7. Android各分辨率定义的图片规格

    我们定义的app图片规格 app图标需要分iphone和android两套 iphone: 名称 Iphone4 Iphone5 手机尺寸 960*640(高*宽) 1136*640 (高*宽) 电池 ...

  8. BZOJ 1597 土地购买(斜率优化DP)

    如果有一块土地的长和宽都小于另一块土地的长和宽,显然这块土地属于“赠送土地”. 我们可以排序一下将这些赠送土地全部忽略掉,一定不会影响到答案. 那么剩下的土地就是长递减,宽递增的.令dp[i]表示购买 ...

  9. Codeforces 1025D(区间dp)

    容易想到设f[i][j][k]为i~j区间以k为根是否能构成bst.这样是O(n4)的.考虑将状态改为f[i][j][0/1]表示i~j区间以i-1/j+1为根能否构成bst.显然如果是i-1作为根的 ...

  10. 【题解】SCOI2008配对

    贪心+dp~观察数据,发现一个规律:将数字排序之后,最优匹配只可能产生在该数字和与它距离不超过二的数字之间. 所以可以用dp[i]代表前i个数(排序)匹配的最小差值,之后暴力选出该新数应该如何匹配. ...