解析

所有方法调用中的目标方法在Class文件里面都是常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用。这种解析的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的,即“编译期可知,运行期不可变”,这类目标的方法的调用称为解析(Resolution)

分派

解析调用一定是个静态的过程,在编译期就完全确定,在类加载的解析阶段就将涉及的符号引用全部转变为可以确定的直接引用,不会延迟到运行期       再去完成。而分派(Dispatch)调用则可能是静态的也可能是动态的。于是分派方式就有静态分派和动态分派。

java是一门面向对象的编程语言,具备面向对象的三个基本特征:继承、封装、多态。

  接下来我将演示下分派调用过程来揭示多态性的一些最基本的体现。如重载(Overload)和重写(Override)在jvm中是如何实现的。

1、静态分派

  静态分派发生在编译阶段,不是由jvm执行的,典型的应用是重载(Overload)。

  静态分派的最直接的解释是在重载的时候是通过参数的静态类型而不是实际类型作为判断依据的。因此在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本。

方法静态分派代码演示:
/**
* 方法静态分派
*
* @author sun
*
*/
public class StaticDispatch {
static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public void sayHello(Human guy) {
System.out.println("hello,guy");
} public void sayHello(Man guy) {
System.out.println("hello,sun");
} public void sayHello(Woman guy) {
System.out.println("hello,ting");
} public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.sayHello(man);
staticDispatch.sayHello(woman);
}
}
运行结果:

hello,guy
hello,guy

对于有经验的程序员来说,出现上面的结果并不感到惊讶。接下来我们要分析为什么会出现这样的结果,但在分析之前,我们要明确两个概念。

即变量的静态类型和变量的实际类型。如下:

Human man = new Man();

 Human 称为变量的静态类型;Man称为变量的实际类型。二者的区别在于静态类型在编译期是可知的,而实际类型是在运行期才确定,编译时并不知道一个对象的实际类型是什么。

// 实际类型变化
Human man = new Man();
man = new Woman();
// 静态类型变化
sr.sayHello((Man) man);
sr.sayHello((Woman) man);

  

在方法接收者已经确定是对象sr的前提下,使用哪个重载版本就完全取决于传入参数的数量和数据类型。代码中刻意使用了两个静态类型相同而实际类型不同的变量,编译器在重载时是通过参数的静态类型而不是实际类型作为判定依据的,man和woman的静态类型都是Human。静态类型在编译期可知,因此在编译阶段,编译期根据man和woman的静态类型为Human的事实,来选择public void sayHello(Human guy)作为调用方法,这就是方法重载的具体体现

所有依赖静态类型来定位方法执行版本的分派动作称为静态分派

2、动态分派

动态分派和多态性的体现-重写(Override)有着本质的联系。

/**
* 方法动态分派
*
* @author sun
*
*/
public class DynemicDispatch { static abstract class Human {
protected abstract void sayHello();
} static class Man extends Human { @Override
protected void sayHello() {
System.out.println("man say hello");
} } static class Woman extends Human { @Override
protected void sayHello() {
System.out.println("woman say hello");
} } public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}  

运行结果:

man say hello
woman say hello
woman say hello

 显然这里不可能根据静态类型来决定调用那个方法。导致这个现象很明显的原因是因为这两个变量的实际类型不一样,jvm根据实际类型来分派方法执行版本。我们使用javap命令来查看这段代码的字节码。以下是该段代码main方法的字节码:

 public static void main(java.lang.String[]);
Code:
0: new #16 // class Demo/DynemicDispatch$Man
3: dup
4: invokespecial #18 // Method Demo/DynemicDispatch$Man."<init>":()V
7: astore_1
8: new #19 // class Demo/DynemicDispatch$Woman
11: dup
12: invokespecial #21 // Method Demo/DynemicDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #22 // Method Demo/DynemicDispatch$Human.sayHello:()V
20: aload_2
21: invokevirtual #22 // Method Demo/DynemicDispatch$Human.sayHello:()V
24: new #19 // class Demo/DynemicDispatch$Woman
27: dup
28: invokespecial #21 // Method Demo/DynemicDispatch$Woman."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #22 // Method Demo/DynemicDispatch$Human.sayHello:()V
36: return

  0~15行的字节码作用是建立man和woman的内存空间、调用Man和Woman类型的实例构造器的。

16、20两句把刚刚创建的两个对象的引用压到栈顶,这两个对象是将要执行的sayHello()方法的所有者,称为接收者(Receiver)!

17、21两句是方法调用指令,这两句指令最终的目标方法并不相同。

这需要说说jvm的invokevirtual指令了,这个指令的解析过程有助于我们更深刻理解重写的本质。该指令的具体解析过程如下:

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

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

  3. 如果在类型C中没有找到,则按照继承关系从下到上依次对C的各个父类进行第2步的搜索和验证过程

  4. 如果始终没有找到合适的方法,则抛出抽象方法错误的异常

从这个过程可以发现,在第一步的时候就在运行期确定接收者的实际类型,所以当调用invokevirtual指令就会把运行时常量池中符号引用解析为不同的直接引用,这就是方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派称为动态分派

jvm004 解析与分派的更多相关文章

  1. JAVA方法调用中的解析与分派

    JAVA方法调用中的解析与分派 本文算是<深入理解JVM>的读书笔记,参考书中的相关代码示例,从字节码指令角度看看解析与分派的区别. 方法调用,其实就是要回答一个问题:JVM在执行一个方法 ...

  2. JVM 方法调用之静态分派

    分派(Dispatch)可能是静态也可能是动态的,根据分派依据的宗量数可分为单分派和多分派.这两种分派方式的两两组合就构成了静态单分派,静态多分派,动态单分派,动态多分派这4种组合.本章讲静态分派. ...

  3. 深入理解Java虚拟机--中

    深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...

  4. 《深入理解Java虚拟机》-----第8章 虚拟机字节码执行引擎——Java高级开发必须懂的

    概述 执行引擎是Java虚拟机最核心的组成部分之一.“虚拟机”是一个相对于“物理机”的概念 ,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上的,而 ...

  5. jvm 字节码执行 (一)方法调用

    “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器.硬件.指令集和操作系统层面上,而虚拟机的执行引擎是 由自己实现的,因此可以自行制定指令集 ...

  6. 深入理解Java虚拟机读书笔记5----虚拟机字节码执行引擎

    五 虚拟机字节码执行引擎   1 运行时栈帧结构     ---栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机运行时数据区中的虚拟机栈的栈元素.     ---栈帧中存储了方法的局部变 ...

  7. Java面试题 BAT 大公司面试题整理总结!

    本文只列出了问题,答案还是需要需要自己的总结,很多时候自己总结出来的语言在面试时比硬背的效果好很多. 这些题目是网友去百度.小米.乐视.美团.58.猎豹.360.新浪.搜狐等一线互联网公司面试被问到的 ...

  8. BATJ等大厂最全经典面试题分享

    金九银十,又到了面试求职高峰期,最近有很多网友都在求大厂面试题.正好我之前电脑里面有这方面的整理,于是就发上来分享给大家. 这些题目是网友去百度.蚂蚁金服.小米.乐视.美团.58.猎豹.360.新浪. ...

  9. tomcat架构分析(connector BIO 实现)

    出处:http://gearever.iteye.com 在tomcat架构分析(概览)中已经介绍过,connector组件是service容器中的一部分.它主要是接收,解析http请求,然后调用本s ...

随机推荐

  1. Java经典编程题50道之十二

    企业发放的奖金根据利润提成:利润(I)低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10%提成, 高于10万元的部分 ,可提成7.5%:20万到40万之间 ...

  2. Java对【JSON数据的解析】--官方解析法

    要求:解析下面5个JSON数据 1.String string ="{name:'zhangsan',age:18}"; 2.String string2 = "{per ...

  3. 一位菜鸟的java 最基础笔记

    java的特性 简单性(Simple). 结构体系中立(Architecture Neutral). 面向对象(Object Oriented). 易于移植(Portable). 分布式(Distri ...

  4. JS学习笔记——数组去重

    <script type="text/javascript"> //indexOf"是ECMAScript5方法,IE8以下不支持,需多写兼容低版本浏览器代码 ...

  5. 利用浏览器查找font-family的css编码

    提供一种利用Chrome快速查找字体编码的小技巧 打开浏览器,按下键盘F12 点击Console控制台 输入escape("要查询的字体中文名称")(注意:括号与引号都是英文输入法 ...

  6. 微信小程序 - 自定义创建

    自定义创建与默认创建完全相同, 只是不要勾选quick start即可 淡定(不要看到报错就紧张, 一定要淡定) 看看它说了什么, no such file or directory(没有文件或目录) ...

  7. flask 扩展之 -- flask-pagedown

    支持 Markdown 语法, 并添加 富文本文章的预览功能. 使用到的包列表: PageDown : 使用 JavaScript 实现的客户端 Markdown 到 HTML 的转换程序. Flas ...

  8. JS常用数据校验集合(adding)

    常用数据校验集合 var _validator = { MAIL_REGEX: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,; ...

  9. Phpcms V9缩略图裁剪存在黑边的解决方法

    最近用Phpcms v9又碰到一个老问题:在内容页缩略图裁剪的时候出现黑边,这种情况很久没碰到,估计是长宽不同或者会在首页.列表页.内容页不同地方偶然出现的情况,在这里分享下Phpcms V9缩略图裁 ...

  10. 如果导入的项目只有源码,可以将其他项目中的.classpath 和 .project复制到根目录下即可。

    如果导入的项目只有源码,没有对应的项目配置如web项目,可以将其他项目中的.classpath 和 .project复制到根目录下即可.