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语言版本的设计模式实现。这里只是抛砖引玉,对于其他几种方法调用的指令,也建议大家尝试进行更多的分析。
相关推荐
在面向对象编程中,权限修饰符是控制类成员(属性和方法)访问范围的关键元素,而方法的重写则是实现多态性的重要方式。下面将详细解释这两种概念。 一、四种权限修饰 1. **private(私有)**:私有成员只能在定义...
下面详细介绍如何在JavaScript中覆盖原有方法来实现重写。 首先,我们要理解JavaScript中的函数是一等公民,这意味着函数可以像其他变量一样被存储在变量中、作为参数传递给其他函数,或者作为其他函数的返回值。...
总结起来,重写全局`new`和`delete`是C++中实现高级内存管理策略的一种方式,它可以用于优化性能、提高内存使用效率或者满足特定的资源管理需求。正确地完成这一过程需要深入理解内存管理,并确保重写后的操作符符合...
"binary-operation-overwrite.rar_operation"这个标题暗示了我们可能在探讨一个与二进制操作相关的代码重写或更新过程,这可能涉及到优化现有代码或实现新的功能。 描述中提到的“Call To Number Source Code”可能...
在探讨如何使`TextBox`控件的输入具备覆盖(overwrite)功能之前,我们先来了解一下`TextBox`控件的基本概念及其在编程中的作用。`TextBox`是Windows Forms中最为常用的控件之一,主要用于接收用户的文本输入。在...
在C++编程语言中,`overload`(重载)、`override`(覆盖)和`overwrite`(重写)是三个关键的概念,它们都涉及到函数的多态性,但各有其特定的含义和规则。接下来,我们将深入探讨这三个概念的区别。 **1. ...
"ASP.net File Uploader with Overwrite Message" 指的是一种实现,它允许用户上传文件,并在文件已存在时显示确认消息,询问用户是否覆盖现有文件。 首先,我们来看看`FileUploader.aspx`文件,这是ASP.NET中的一...
在子类中定义与父类同名、同参数列表的方法即为重写。重写后,当通过子类实例调用该方法时,会执行子类的版本,而不是父类的。 5. 自动转型: Java支持向上转型,即子类对象可以被当作父类对象使用。这允许我们在...
写在开篇的话此项目,将长期更新,会陆续讲解一些常用方法的overwrite,欢迎各位star。由于项目本身没有很复杂的结构,所以这边本地服务目前只用了http-server和基础的webpack-dev-server服务,后期会不断进行完善。...
在"压缩包子文件的文件名称列表"中,虽然我们没有具体的文件详情,但可以推测这个项目可能包含了以下内容:Spring和SpringMVC的配置文件(如spring-context.xml、spring-mvc.xml)、MyBatis的映射文件(如Mapper接口...
在Java中,继承通过`extends`关键字实现,子类不仅继承了父类的所有非私有成员,还可以添加自己的属性和方法,甚至重写父类的方法以适应特定需求。 #### 继承的特点: 1. **层次结构**:类之间的继承关系形成了一...
在实际操作中,Font Overwrite-crx的设置通常包括字体选择、字体大小、行高和段落间距等选项,以满足不同用户的个性化需求。同时,它还可能提供预设的主题或模板,让用户一键应用常见或流行的字体组合。 至于【Font...
所有的下载都覆盖了现有的文件,而不是添加了(1)”、“(2),等等,包括修复文件夹。 与原始的“下载覆盖现有文件”非常相似。...这不会在覆盖之前比较文件的内容,而只是比较文件名。 支持语言:English (United States)
1.1 方法重写(Overwrite)是指在子类中重新定义父类中的某个方法,保持方法名和参数列表一致,但提供不同的实现。这样,当使用子类对象调用这个方法时,会执行子类的实现,即使通过父类引用调用也会运行子类版本。...
在Delphi中,我们可以利用第三方组件或者内置的 Indy 库来实现FTP功能。Indy 是一个强大的网络通信库,提供了许多网络协议的支持,包括FTP。 首先,我们需要在Delphi项目中引入Indy的相关组件。在Component Palette...
Override Overload Overwrite 马克-to-win Java视频
通过上述内容可以看出,在ArcGIS Engine中使用`Geoprocessor`类可以很方便地实现投影转换的功能。不过需要注意的是,实际应用中可能还需要考虑到更多的细节问题,例如输入数据的有效性验证、输出路径的合法性检查等...
虚函数和纯虚函数在C++中用于实现多态,虚函数允许派生类重写基类的方法;纯虚函数是抽象类中没有实现的虚函数,它要求派生类必须提供实现。 menset()函数是C语言中的内存设置函数,它将一块内存区域中的每个字节都...