Java是解释执行的,包括动态链接的特性,都给解析或运行期间提供了很多灵活扩展的空间。面向对象语言的继承、封装和多态的特性,在JVM中是怎样进行编译、解析,以及通过字节码指令如何确定方法调用的版本是本文如下要探讨的主要内容,全文围绕一个多态的简单举例来看在JVM中是如何实现的。
先简单介绍几个概念。对于字节码执行模型及字节码指令集的相关概念可以参考之前的一篇介绍http://yunshen0909.iteye.com/blog/2220937。
一、方法调用的解析
在Class文件中,方法调用是常量池中的一个符号引用,在加载的解析期或者运行时才能确定直接引用。
下面两种指令是在解析期就可以确定直接引用,调用的对应方法也叫作非虚方法。
1、 invokestatic 主要用于调用静态方法
2、 invokespecial 主要用于调用私有方法,构造器,父类方法。
下面两种是在运行时才能确定直接引用的,但是除了final方法,final方法也可以在解析期确定方法的调用版本。
1、 invokevirtual 虚方法,不确定调用那一个实现类
2、 invokeinterface 接口方法,运行时才能确定实现接口的对象。
二、动态分派
先回顾一下静态和动态分派的概念。Java中,所有以静态类型来定位方法执行版本的分派动作,都称为静态分派。其实也就是重载(Overload)就是一种典型的静态分派,在编译期就可以知道方法调用的实际版本。相对得,动态分派是需要在运行期才能确定方法的版本,也就是直接引用,一种典型应用就是重写(OverWrite)。在调用invokevirtual指令时,把常量池中的类方法符号引用解析到直接引用的过程就是重写的过程,运行期根据实际类型确定方法的执行版本。
三、解释执行
Java的解释执行机制,使jaavc编译的过程涵盖了从程序代码的语法、词法分析,再到AST(抽象语法树)生成线性的字节码指令流的过程。而解释执行是在JVM内部,基于栈的指令集提供了整个平台的可移植性支撑,所以这也是java执行慢的要因,因为不像编译执行,过程中需要更多的出入栈指令,而栈又是内存的一个区块,对内存的频繁访问降低了性能。
四、重写(OverWrite)举例
下面通过一个简单的例子来看一下重写在JVM中的字节码执行模型。
父类:
package bytecode;
/**
* Created by yunshen.ljy on 2015/7/27.
*/
public class Wine {
public String drink(int ml) {
return "drink " + ml + "ml wine";
}
}
子类:
package bytecode;
/**
* Created by yunshen.ljy on 2015/7/27.
*/
public class Beer extends Wine{
/**
* 重写父类方法,实现多态
*/
public String drink(int ml){
return "drink " + ml +"ml beer";
}
}
调用:
package bytecode;
/**
* Created by yunshen.ljy on 2015/7/26.
*/
public class MethodInvotionTest {
public String drink(int ml) {
Wine wines = new Beer();
return wines.drink(ml);
}
}
我们都知道,方法调用返回的结果是“drink XX ml beer”。但是在编译期,字节码中的指令是无法确定实际调用的方法版本的。下面看一下调用方法的字节码结构。
public java.lang.String drink(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: new #2 // class bytecode/Beer
3: dup
4: invokespecial #3 // Method bytecode/Beer."<init>":()V
7: astore_2
8: aload_2
9: iload_1
10: invokevirtual #4 // Method bytecode/Wine.drink:(I)Ljava/lang/String;
13: areturn
LineNumberTable:
line 10: 0
line 12: 8
第一条指令new 创建对象,把引用入栈指令,new 指令后面的#4就是前文提到的,对于运行时常量池的一个引用,只是javap命令处理成比较易懂的方式来显示。接着,后面的dup 指令复制刚放入的引用(操作数栈栈顶的值的复制并且将这个“副本”放到栈顶)。Invokespecial 指令就是之前介绍的非虚方法调用指令,在操作数栈中通过其中的一个引用调用Beer的构造器,初始化对象,让另一个相同引用指向初始化的对象,然后前一个引用(this)弹出栈。astore_2把引用保存到局部变量表中的索引2位置中。aload_2 把刚才局部变量表中索引2处的值压入操作栈。iload_1将int参数,也就是局部变量表中索引1处的值压入操作数栈。Invokevirtual指令将执行操作数栈中的两个值出栈,执行方法调用,指令后的#4只是常量池中的一个符号引用,只有在运行时,才能确定方法的直接引用。最后的areturn语句将方法执行结果的值出栈,消除当前的栈帧,如果上层有对于当前方法的继续调用,那么会将调用的方法的栈帧设置成当前栈帧(current stack frame)。
下面通过对比每条指令执行后,局部变量表和操作数栈中的值来加强一下上述的出栈、入栈操作。首先通过stack=2, locals=3, args_size=2 ,先知道了方法的局部变量表是占用了3个slot,操作数栈的size是2个slot。而因为我们的方法是实例方法(非类方法),所以隐含的this关键字的指令我们先描述到局部变量表的第一个位置,占用一个slot。(以下不再画出常量池的结构,可参照calss文件自行分析)
这里只是个简单的举例,和一些知识的回顾。知道JVM的执行引擎和字节码指令的一些概念,会让我们对于程序执行的结果预期更加准确,也更易理解一些java语言版本的设计模式实现。这里只是抛砖引玉,对于其他几种方法调用的指令,也建议大家尝试进行更多的分析。
相关推荐
Java面向对象(进阶)-- 四种权限测试与方法的重写(override_overwrite)
重写Global new/delete示例
方法重写(Override/Overwrite):是指子类重写了从父类继承过来的方法。 区别:方法重载没有继承、属于一种静态联编技术。 方法重写有继承、属于动态联编技术。 10.什么是抽象类?什么是接口?抽象类与接口的区别?
写在开篇的话此项目,将长期更新,会陆续讲解一些常用方法的overwrite,欢迎各位star。由于项目本身没有很复杂的结构,所以这边本地服务目前只用了http-server和基础的webpack-dev-server服务,后期会不断进行完善。...
以下是对C++中overload,override,overwrite的区别进行了详细的分析介绍,需要的朋友可以过来参考下
ASP.net File Uploader with Overwrite Message。
所有的下载都覆盖了现有的文件,而不是添加了(1)”、“(2),等等,包括修复文件夹。 与原始的“下载覆盖现有文件”非常相似。...这不会在覆盖之前比较文件的内容,而只是比较文件名。 支持语言:English (United States)
Override Overload Overwrite 马克-to-win Java视频
https://microsoftededd.microsoft.com/addons/detail/dfnblaebmpjlokbkadgeaghajajidojj - 请参阅这里的源代码:https://github.com/zach-adams/downloads-overwrite-already-existing-files - 我对丢失/覆盖...
让TextBox的输入具备overwrite(覆盖)的功能
并可解决JMF安装文件始终报overwrite无法安装的问题。Exception in thread "VFW Request Thread" java.lang.UnsatisfiedLinkError: JMFSecurityManager: java.lang.UnsatisfiedLinkError: no jmvfw in java.library....
在百度查阅了很多篇文章总是出现错误,有的让下软件,有的让查日志,有的让调网络,各种各样五花八门,却没有一点点用,非常花里胡哨,帮助我在2020年整好这个问题必有重谢,我女友非得让我整这个,之后给他朋友装逼...
重载(overload)和重写(overwrite)区别 StringBufferStringBuilderString区别 冒泡排序 选择排序 单例设计模式 常见的异常类型 Throws和Throw的区别 Final,Finally,finalize关键字的作用 Hashtable与HashMap的...
语言:English 覆盖网站的字体。 如果不喜欢特定字体,请使用此。 (例如Gulim,Comic Sans)
管理系统,SSM整合
Call To Number Source Code for Java Script.
1、在某一个磁盘下新建一个文件夹:例如在E盘下创建一个mybatisTable,将mybatis-3.2.2.jar,mybatis-generator-core-1.3.2.jar,mysql-connector-java-5.1.24-bin.jar放入mybatisTable文件夹中。 2、新建一个...
3:缓冲溢出:缓冲超过区域,所以重写了边界(Place more than the buffer can hold, so overwrite the boundary )。 4:静态分配:程序在编译和链接阶段为变量分配内存空间。静态分配的变量在main之前分配内存和...