解析

所有方法调用中的目标方法在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. 从Thread,ThreadPool,Task, 到async await 的基本使用方法解读

    记得很久以前的一个面试场景: 面试官:说说你对JavaScript闭包的理解吧? 我:嗯,平时都是前端工程师在写JS,我们一般只管写后端代码. 面试官:你是后端程序员啊,好吧,那问问你多线程编程的问题 ...

  2. SQL SERVER大话存储结构(6)_数据库数据文件

            数据库文件有两大类:数据文件跟日志文件,每一个数据库至少各有一个数据文件或者日志文件,数据文件用来存储数据,日志文件用来存储数据库的事务修改情况,可用于恢复数据库使用.     这里分 ...

  3. 命令查看服务器SN号

    今天工作的时候,为了检查一台服务器的序列号,没必要在跑到机房里了,所以在系统下就可以看机器序列号了.如下: 1.linux取序列号: 命令执行:dmidecode |grep "Serial ...

  4. JS中的函数传参

    前言: 函数分为有参有返回值,有参无返回值,无参无返回值,无参有返回值:那么对于无参数的函数你想使用函数的调用怎么办呢?如果你想封装一个代码,实现多种功能,但是形参大于实参或者实参大于形参又该如何?本 ...

  5. OSS web直传 ajax方式 上传图片、文件

    部分js代码 send_request = function(){//这是从后台获取认证策略等信息. var htmlobj=$.ajax({url:root+"/service/polic ...

  6. python爬虫番外篇(一)进程,线程的初步了解

    一.进程 程序并不能单独和运行只有将程序装载到内存中,系统为他分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别在于:程序是指令的集合,它是进程的静态描述文本:进程是程序的一次执行活动, ...

  7. Vulkan Tutorial 14 Integration pipeline

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 我们现在整合前几章节的结构体和对象创建图形管线!以下是我们现在用到的对象类型,作为一 ...

  8. [1] C# IS & AS讲解

    c# 中 is和as 操作符是用来进行强制类型转换的 is : 检查一个对象是否兼容于其他指定的类型,并返回一个Bool值,永远不会抛出异常 object o = new object(); if ( ...

  9. Behavior的使用(一):页面跳转NavigateToPageAction

    Behavior的使用,让UI设计师能够更加方便的进行UI设计,更高效地和开发进行合作.Behavior有三种触发方式:EventTriggerBehavior事件触发,DataTriggerBeha ...

  10. docker 汇总

    整理中 ... 一. docker 基础 配置参数最佳实践 二. docker 编排工具 docker swarm mesos kubernetes AWS ECS 三. docker 生态 dock ...