从JVM角度看Java多态
首先,明确一下,Java多态的三个必要条件:
1、 继承
2、 子类重写父类方法
3、 父类引用指向子类对象
然后看一个例子
- package test.xing;
- class Father{
- protected int age;
- public Father(){
- age = 40;
- }
- void eat(){
- System.out.println("父亲在吃饭");
- }
- }
- class Child extends Father{
- protected int age;
- public Child(){
- age = 18;
- }
- void eat(){
- System.out.println("孩子在吃饭");
- }
- void play(){
- System.out.println("孩子在打CS");
- }
- }
- public class TestPolymorphic {
- public static void main(String[] args) {
- Father c = new Child();
- c.eat();
- //c.play();
- System.out.println("年龄:"+c.age );
- }
- }
输出结果为:
给出结论:当满Java多态的三个条件时,可以发现c.eat()调用的实际上是子类的eat,但c.age调用的还是父类的age,而c.play()则不会通过编译。
下面从JVM的角度解释上面这种现象
我们就从Father c = new Child()这句话切入
这句话首先会执行new Child(),在堆中分配一个对象。
当然在分配Child类的实例时,先要通过JVM的类加载器将Child类对应的class文件加载到JVM中,然后JVM根据class文件中的字节流产生一个表示class文件中类型信息的结构体
这个结构体的具体实现,不同的JVM会有不同的实现方式,但大致上都差不多,都要遵守JVM的规范。
这个表示class文件中类型信息的结构体大概由以下几部分构成:
1、 常量池
2、 类变量(静态变量)
3、 字段信息
4、 方法信息
5、 类的父类信息
6、 类的访问权限信息等
这些我说的也不够准确,具体的大家可以看JVM相关的书籍如《深入理解Java虚拟机》,在这里就有个大概的概念就好。
之后,JVM会根据上面这个结构体生成一个叫做方法表的东西。这个方法表是实现java多态的一个关键。
方法表中包含的是实例方法(就是相对于静态方法而言的,用对象访问的那些方法)的直接引用,也就是说通过这个方法表就能够访问到该类的实例方法,
而且,这些实例方法不仅包括本类的方法,还包括其父类的实例方法,以及父类的父类的实例方法(就是一直到Object)。
而且,这些方法中不包含私有方法(因为私有方法不能继承)
方法表中的这些直接应用会指向到JVM中表示类型信息的那个结构体(就是上面那个结构体)的相应的方法信息(就是上面结构体中4的某个位置),当然这只是本类的方法,表中还有父类的方法,相应地指向父类类型信息结构体的具体位置。
可能表达的不够清晰,下面画个图表示。
上面提到过,方法表中不仅包括本类的方法,还包括父类的方法,方法表值这样产生的,以Child类的方法表为例:
首先方法表中,会产生指向继承自Object类的方法的引用,这些包括指向toString的和指向equals的,当然Object中还包括很多方法,这里就不写了
然后方法表中产生指向继承自Parent类的方法的引用,这包括eat,
最后产生指向本类的方法的引用。
这里需要注意的一点是,当Child类的方法表产生指向Parent类中的方法的引用时,会有一个指向eat方法的引用,最后产生指向本类的方法的引用时,也有一个指向eat的引用,这时候,新的数据会覆盖原有的数据,也就是说原来指向Parent.eat的那个引用会被替换成指向Child.eat的引用(占据原来表中的位置)。所以我们看到在Child类的方法表中指向的是Child.eat而Parent类的方法表中指向的是Parent.eat。子类的方法表中就没有指向Parent.eat的引用了。
而且还要注意一个特点就是,Parent和Child的方法表中,指向eat的引用在表中的偏移量是一样的,都是第三个位置。(这是因为子类eat方法覆盖掉了父类eat方法,占据了原来父类eat方法的引用在表中的位置)
这里再多说一句,表示类型信息的结构体中,的方法信息,只包含本类特有的,或者是重写的方法信息,没有父类的方法信息。
了解了方法区的结构后,我们来看堆中对象的结构
接下来是栈区,产生Father类型的引用,这个引用指向堆区中的Child类的实例。
这里需要解释一下Father c的含义,我们知道c表示一个引用,这个引用指向堆中的Child类的实例,说白了就是一个地址,这个地址指向堆中的Child的类的实例,但是我们不要忘记前面还有一个Father修饰这个c
我们都知道在c中有void类型的指针,而给指针前面限定一个类型就限制了指针访问内存的方式,比如char * p就表示p只能一个字节一个字节地访问内存,但是int *p中p就必须四个字节四个字节地访问内存。
但是我们都知道指针是不安全的,其中一个不安全因素就是指针可能访问到没有分配的内存空间,也就是说char *虽然限制了p指针访问内存的方式,但是没有限制能访问内存的大小,这一点要完全靠程序员自己掌握。
但是在java的引用中Father不但指定了c以何种方式访问内存,也规定了能够访问内存空间的大小。
我们看Father实例对象的大小是占两行,但Child实例对象占三行(这里就是简单量化一下)。
所以虽然c指向的是Child实例对象,但是前面有Father修饰它,它也只能访问两行的数据,也就是说c根本访问不到Child类中的age!!!只能访问到Father类的age,所以输出40
而且我们注意两个类的方法表:
我们看到Parent的方法表占三行,Child的方法表占4行,c虽然指向了Child类的实例对象,而对象中也有指针指向Child类的方法表,但是由于c受到了Father的修饰,通过c也只能访问到Child方法表中前3行的内容!!!!
然而前面说过,在方法表的形成过程中,子类重写的方法会覆盖掉表中原来的数据,也就是Child类的方法表的第三行是指向Child.eat的引用,而不是指向Parent.eat(因为方法表产生了覆盖),所以c访问到的是Child.eat。也就是子类的方法!!!这种情况下,c是没有办法直接访问到父类的eat方法的。
以上就是对输出结果的解释。
花了大概两天的时间看JVM虚拟机,看得不够仔细,纰漏之处还请之处。谢谢。
从JVM角度看Java多态的更多相关文章
- 从虚拟机角度看Java多态->(重写override)的实现原理
工具与环境:Windows 7 x64企业版Cygwin x64jdk1.8.0_162 openjdk-8u40-src-b25-10_feb_2015Vs2010 professional 0x0 ...
- 从JVM的角度看JAVA代码--代码优化
从JVM的角度看JAVA代码–代码优化 从JVM的角度看JAVA代码代码优化 片段一反复计算 片段二反复比較 在JVM载入优化为class文件,运行class文件时,会有JIT(Just-In-Tim ...
- 从设计模式的角度看Java程序优化
一.前言 Java程序优化有很多种渠道,比如jvm优化.数据库优化等等,但都是亡羊补牢的措施,如果能在设计程序架构时利用设计模式就把程序的短板解决,就能使程序更加健壮切容易维护迭代 二.常用的设计模式 ...
- 从字节码的角度看Java内部类与外部类的互相访问
Java中non-static内部类为何可以访问外部类的变量?Java中外部类又为何可以访问内部类的private变量?这两个问题困扰过我一段时间,查了一些网上的答案,大多从“闭包”概念入手,理解起来 ...
- 从成本角度看Java微服务
近年来,微服务因其良好的灵活性和伸缩性等特点备受追捧,很多公司开始采用微服务架构或将已有的单体系统改造成微服务.IBM也于近日开源了轻量级Java微服务应用服务器 Open Liberty .但是采用 ...
- 从JDK源码角度看java并发线程的中断
线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠 ...
- 从jvm角度看懂类初始化、方法重写、重载。
类初始化 在讲类的初始化之前,我们先来大概了解一下类的声明周期.如下图 类的声明周期可以分为7个阶段,但今天我们只讲初始化阶段.我们我觉得出来使用和卸载阶段外,初始化阶段是最贴近我们平时学的,也是笔试 ...
- [转]从JVM角度看线程安全与垃圾收集
线程安全 Java内存模型中,程序(进程)拥有一块内存空间,可以被所有的线程共享,即MainMemory(主内存):而每个线程又有一块独立的内存空间,即WorkingMemory(工作内存).普通情况 ...
- 从JVM角度看i++ 与++i
1.i++和++i的问题 反编译结果为 Code: 0: iconst_1 1: istore_1 2: iinc 1, 1 //这个个指令,把局部变量1,也就是i,增加1,这 ...
随机推荐
- 网页html结构搭建方法总结
在div+css布局中,一般都这样来整体构架的: <div id="header"></div><div id="center"& ...
- web与request
request --> 封装了客户端所有的请求数据! 请求行 请求头 空行 请求体(GET没体) 回忆一下http协议!请求协议中的数 ...
- Mycat安装与使用
1.下载: https://github.com/MyCATApache/Mycat-download 具体下载哪个版本以发布为准,推荐1.4,1.5. 2.安装: 安全前,在Linu ...
- Androidstudio2.0.0下载,欢迎下载
Androidstudio开发工具很实用,但是没了Google下载还是蛮麻烦的,所以呢Eric就向大家提供了下载的地址,其实就是从自己网盘考过来的,大家可以下载的. http://pan.baidu. ...
- 老李教你性能测试监控工具nmon
老李教你性能测试监控工具nmon loadrunner的某些性能监控器不够强大,这就需要我们利用更好的工具进行监控,在项目中我们会用nmon工具作为辅助性能监控的工具,帮助我们进行性能分析,pop ...
- Centos安装MySql、Java、Tomcat
一.安装MySql 安装MySql yum install -y mysql-server mysql mysql-devel 启动MySql服务 service mysqld start 为root ...
- Redis基础学习(二)—数据类型
一.Redis支持的数据类型 Redis中存储数据是通过key-value存储的,对于value的类型有以下几种: (1)字符串. (2)Map (3)List (4)Set public cla ...
- Python JavaScript概述
一.如何编写? 1.JavaScript代码存在形式 <!DOCTYPE html> <html> <head> <meta http-equiv=" ...
- require.js疑惑
昨天小颖分享了一篇require.js入门 ,今天小颖发现了一个很郁闷的问题,希望大神们帮小颖解释下到底是什么原理才能出现以下的现象,其实小颖昨天也有问过园友里的一位帅锅,只是他解释的小颖没太明白.嘻 ...
- 第十章 MyBatis入门
第十章 MyBatis入门10.1 MyBatis入门 优点:简单且功能强大.能够完全控制SQL语句.容易维护和修改 缺点:移植性不好 使用步骤: 1.下载 ...