java多态的实现原理(JVM调用过程)(综合多篇文章,参考见文末)
一个对象变量可以指示多种实际类型的现象称为多态
允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖正体现了多态。
1、多态的机制
1.1 本质上多态分两种
1、编译时多态(又称静态多态) 2、运行时多态(又称动态多态)
重载(overload 发生在一个类中,方法名必须相同,不同参数)就是编译时多态的一个例子,编译时多态在编译时就已经确定,运行时运行的时候调用的是确定的方法。
我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定。这也是为什么有时候多态方法又被称为延迟方法的原因。
下面简要介绍一下运行时多态(以下简称多态)的机制。
1.2 多态通常有两种实现方法
1、子类继承父类(extends) 2、类实现接口(implements)
无论是哪种方法,其核心之处就在于对父类方法的改写或对接口方法的实现,以取得在运行时不同的执行效果。
要使用多态,在声明对象时就应该遵循一条法则:声明的总是父类类型或接口类型,创建的是实际类型。
2、多态的实现原理
Java 的方法调用方式
Java 的方法调用有两类,动态方法调用与静态方法调用。静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。类调用 (invokestatic) 是在编译时刻就已经确定好具体调用方法的情况,而实例调用 (invokevirtual) 则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。
JVM 的方法调用指令有四个,分别是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前两个是静态绑定,后两个是动态绑定的。本文也可以说是对于 JVM 后两种调用实现的考察。
常量池(constant pool)
常量池中保存的是一个 Java 类引用的一些常量信息,包含一些字符串常量及对于类的符号引用信息等。Java 代码编译生成的类文件中的常量池是静态常量池,当类被载入到虚拟机内部的时候,在内存中产生类的常量池叫运行时常量池。
常量池在逻辑上可以分成多个表,每个表包含一类的常量信息,本文只探讨对于 Java 调用相关的常量池表。
CONSTANT_Utf8_info
字符串常量表,该表包含该类所使用的所有字符串常量,比如代码中的字符串引用、引用的类名、方法的名字、其他引用的类与方法的字符串描述等等。其余常量池表中所涉及到的任何常量字符串都被索引至该表。
CONSTANT_Class_info
类信息表,包含任何被引用的类或接口的符号引用,每一个条目主要包含一个索引,指向 CONSTANT_Utf8_info 表,表示该类或接口的全限定名。
CONSTANT_NameAndType_info
名字类型表,包含引用的任意方法或字段的名称和描述符信息在字符串常量表中的索引。
CONSTANT_InterfaceMethodref_info
接口方法引用表,包含引用的任何接口方法的描述信息,主要包括类信息索引和名字类型索引。
CONSTANT_Methodref_info
类方法引用表,包含引用的任何类型方法的描述信息,主要包括类信息索引和名字类型索引。
图 2. 常量池各表的关系
可以看到,给定任意一个方法的索引,在常量池中找到对应的条目后,可以得到该方法的类索引(class_index)和名字类型索引 (name_and_type_index), 进而得到该方法所属的类型信息和名称及描述符信息(参数,返回值等)。注意到所有的常量字符串都是存储在 CONSTANT_Utf8_info 中供其他表索引的。
方法表与方法调用
方法表是动态调用的核心,也是 Java 实现动态调用的主要方式。它被存储于方法区中的类型信息,包含有该类型所定义的所有方法及指向这些方法代码的指针,注意这些具体的方法代码可能是被覆写的方法,也可能是继承自基类的方法。
如有类定义 Person, Girl, Boy,
清单 1
class Person {
public String toString(){
return "I'm a person.";
}
public void eat(){}
public void speak(){} } class Boy extends Person{
public String toString(){
return "I'm a boy";
}
public void speak(){}
public void fight(){}
} class Girl extends Person{
public String toString(){
return "I'm a girl";
}
public void speak(){}
public void sing(){}
}
当这三个类被载入到 Java 虚拟机之后,方法区中就包含了各自的类的信息。Girl 和 Boy 在方法区中的方法表可表示如下:
图 3.Boy 和 Girl 的方法表
可以看到,Girl 和 Boy 的方法表包含继承自 Object 的方法,继承自直接父类 Person 的方法及各自新定义的方法。注意方法表条目指向的具体的方法地址,如 Girl 的继承自 Object 的方法中,只有 toString() 指向自己的实现(Girl 的方法代码),其余皆指向 Object 的方法代码;其继承自于 Person 的方法 eat() 和 speak() 分别指向 Person 的方法实现和本身的实现。
Person 或 Object 的任意一个方法,在它们的方法表和其子类 Girl 和 Boy 的方法表中的位置 (index) 是一样的。这样 JVM 在调用实例方法其实只需要指定调用方法表中的第几个方法即可。
如调用如下:
清单 2
class Party{
…
void happyHour(){
Person girl = new Girl();
girl.speak();
…
}
}
当编译 Party 类的时候,生成 girl.speak()
的方法调用假设为:
Invokevirtual #12
设该调用代码对应着 girl.speak(); #12 是 Party 类的常量池的索引。JVM 执行该调用指令的过程如下所示:
图 4. 解析调用过程
JVM 首先查看 Party 的常量池索引为 12 的条目(应为 CONSTANT_Methodref_info 类型,可视为方法调用的符号引用),进一步查看常量池(CONSTANT_Class_info,CONSTANT_NameAndType_info ,CONSTANT_Utf8_info)可得出要调用的方法是 Person 的 speak 方法(注意引用 girl 是其基类 Person 类型),查看 Person 的方法表,得出 speak 方法在该方法表中的偏移量 15(offset),这就是该方法调用的直接引用。
当解析出方法调用的直接引用后(方法表偏移量 15),JVM 执行真正的方法调用:根据实例方法调用的参数 this 得到具体的对象(即 girl 所指向的位于堆中的对象),
具体过程:
假设类B是类A的子类,以 A a = new B() 为例
① A a 作为一个引用类型数据,存储在JVM栈的本地变量表中。
② new B()作为实例对象数据存储在堆中
B的对象实例数据(接口、方法、field、对象类型等)的地址也存储在堆中
B的对象的类型数据(对象实例数据的地址所执行的数据)存储在方法区中,方法区对象类型数据中有一个指向该类方法的方法表。
③Java虚拟机规范中并未对引用类型访问具体对象的方式做规定,目前主流的实现方式主要有两种:
1. 通过句柄访问
在这种方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。
2.通过直接指针访问
通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。
④实现过程
首先虚拟机通过reference类型(A的引用)查询java栈中的本地变量表,得到堆中的对象类型数据的地址,从而找到方法区中的对象类型数据(B的对象类型数据) ,然后查询方法表定位到实际类(B类)的方法运行。
据此得到该对象对应的方法表 (Girl 的方法表 ),进而调用方法表中的某个偏移量所指向的方法(Girl 的 speak() 方法的实现)。
参考:http://www.cnblogs.com/loveincode/p/7230448.html;
https://blog.csdn.net/huangrunqing/article/details/51996424;
http://www.codes51.com/article/detail_701880.html;
java多态的实现原理(JVM调用过程)(综合多篇文章,参考见文末)的更多相关文章
- [ZZ]如果有人问你数据库的原理,叫他看这篇文章
如果有人问你数据库的原理,叫他看这篇文章 http://blog.jobbole.com/100349/ 文章把知识链都给串起来,对数据库做一个概述. 合并排序 阵列.树和哈希表 B+树索引概述 数据 ...
- Java 多态的实现原理
一个对象变量可以指示多种实际类型的现象称为多态 允许不同类的对象对同一消息做出响应.方法的重载.类的覆盖正体现了多态. 1.多态的机制 1.1 本质上多态分两种 .编译时多态(又称静态多态) .运行时 ...
- RPC原理及其调用过程
远程过程调用,简称为RPC,是一个计算机通信协议,它允许运行于一台计算机的程序调用另一台计算机的子程序,而无需额外地为这个交互作用编程. RPC与传统的HTTP对比 优点: 1. 传输效率高(二进制传 ...
- 面试又被 Java 基础难住了?推荐你看看这篇文章。
本文已经收录自 JavaGuide (59k+ Star):[Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识. 1. 面向对象和面向过程的区别 面向过程 :面向过程性能比面 ...
- Java多态的实现原理
1.多态的定义:指允许不同类的对象,对同一消息作出响应: 即同一消息可以根据发送对象的不同采用多种不同的行为方式: 2.多态的实现技术:动态绑定: 指在执行期间判断所引用对象的实际类型,根据其实际的类 ...
- Java基础(47):插入排序的Java封装(含原理,可运行,哨兵位的理解见VisualGo上面的动态分析)
直接插入排序(Straight Insertion Sorting)的基本思想:在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使 ...
- 异步文件通道Java NIO你需要了解多少,来看看这篇文章
在Java 7,AsynchronousFileChannel 被添加到了Java NIO中.使用AsynchronousFileChannel可以实现异步地读取和写入文件数据. 创建一个Asynch ...
- Java分布式锁,搞懂分布式锁实现看这篇文章就对了
随着微处理机技术的发展,人们只需花几百美元就能买到一个CPU芯片,这个芯片每秒钟执行的指令比80年代最大的大型机的处理机每秒钟所执行的指令还多.如果你愿意付出两倍的价钱,将得到同样的CPU,但它却以更 ...
- web漏洞原理 (需要每周更新此篇文章)
SQL注入攻击简介 结构化查询语言SQL是用来和关系数据库进行交互的文本语言.它允许用户对数据进行有效的管理,包含了对数据的查询.操作.定义和控制等几个方面,例如向数据库写入.插入数据,从数据库读取数 ...
随机推荐
- hdu 5977 Garden of Eden(点分治+状压)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5977 题解:这题一看就知道是状压dp然后看了一下很像是点分治(有点明显)然后就是简单的点分治+状压dp ...
- 牛客多校第五场 E room 二分图匹配 KM算法模板
链接:https://www.nowcoder.com/acm/contest/143/E来源:牛客网 Nowcoder University has 4n students and n dormit ...
- hdu 1024 Max Sum Plus Plus(简单dp)
题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1024 题意:给定一个数组,求其分成m个不相交子段和的最大值. 这题有点问题其实m挺小的但题目并没有给出 ...
- Spring的事件监听机制
最近公司在重构广告系统,其中核心的打包功能由广告系统调用,即对apk打包的调用和打包完成之后的回调,需要提供相应的接口给广告系统.因此,为了将apk打包的核心流程和对接广告系统的业务解耦,利用了spr ...
- springboot应用监控和管理
spring boot应用监控和管理 Spring Boot 监控核心是 spring-boot-starter-actuator 依赖,增加依赖后, Spring Boot 会默认配置一些通用的监控 ...
- 在一个升序数组中添加最少的数字,使得从1--n之间所有的数都能用数组中几个数的和表示
一个Java的笔试题上面遇到的题,当时没有做出来. 拆分: 序列升序 1--n所有的数都要能表示 用数组中数字的和表示 添加最少的数字 思路:这个要先从小的数开始表示,因为大的数可以用小数表示. 1- ...
- Redis在新项目中的使用场景
Redis在新项目中的使用场景 数据类型 使用场景 string 比如说,我想知道什么时候封锁一个Ip地址,Incrby命令(使用这个命令记录被访问的次数) Hash 存储用户的信息[id,name, ...
- 干货| 外卖点餐系统(App及后台)
简单总结一下,这个系统是一个外卖点餐系统,也就是仿美团饿了么,也是当时我的毕业设计,花费了我很多精力,主要包括了移动端App.服务端(中台)和商家的后台管理.答辩完后我就将移动端源码放到了GitHub ...
- scala函数式编程(二) scala基础语法介绍
上次我们介绍了函数式编程的好处,并使用scala写了一个小小的例子帮助大家理解,从这里开始我将真正开始介绍scala编程的一些内容. 这里会先重点介绍scala的一些语法.当然,这里是假设你有一些ja ...
- FBCTF平台安装
一言难尽 = =开始不知道FBCTF只能安装在Ubuntu,在本地搭建半天好不容易弄起了PHP环境,打开错误,后来才知道只能在Ubuntu 14.04 LTS下安装= = FBCTF是Facebook ...