Java是解释执行的。包含动态链接的特性。都给解析或执行期间提供了非常多灵活扩展的空间。面向对象语言的继承、封装和多态的特性,在JVM中是怎样进行编译、解析,以及通过字节码指令怎样确定方法调用的版本号是本文例如以下要探讨的主要内容。全文环绕一个多态的简单举例来看在JVM中是怎样实现的。

先简介几个概念。对于字节码运行模型及字节码指令集的相关概念能够參考之前的一篇介绍http://blog.csdn.net/lijingyao8206/article/details/46562933

一、方法调用的解析

在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。

而由于我们的方法是实例方法(非类方法),所以隐含的thiskeyword的指令我们先描写叙述到局部变量表的第一个位置。占用一个slot。(以下不再画出常量池的结构,可參照calss文件自行分析)

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

这里仅仅是个简单的举例。和一些知识的回想。知道JVM的运行引擎和字节码指令的一些概念,会让我们对于程序运行的结果预期更加准确,也更易理解一些java语言版本号的设计模式实现。这里仅仅是抛砖引玉,对于其它几种方法调用的指令。也建议大家尝试进行很多其它的分析。

从字节码指令看重写在JVM中的实现的更多相关文章

  1. 从jvm字节码指令看i=i++和i=++i的区别

    1. 场景的产生 先来看下下面代码展示的两个场景 @Testvoid testIPP() { int i = 0; for (int j = 0; j < 10; j++) { i = i++; ...

  2. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  3. [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式

      前言简介   前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...

  4. jvm理论-字节码指令

    Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成. 基本数据类型 1.除了l ...

  5. JVM学习第三天(JVM的执行子系统)之字节码指令

    早上看了Class类文件结构,晚上继续来看字节码指令,毕竟谁也不是一步登天的(说白了还是穷); 字节码指令 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码,Opcode ...

  6. JVM总括三-字节码、字节码指令、JIT编译执行

    JVM总括三-字节码.字节码指令.JIT编译执行 目录:JVM总括:目录 java文件编译后的class文件,java跨平台的中间层,JVM通过对字节码的解释执行(执行模式,还有JIT编译执行,下面讲 ...

  7. 深入了解java虚拟机(JVM) 第十章 字节码指令

    一.字节码指令的含义 Java字节码指令由一个字节长度的,代表某种特定操作含义的数字(操作码)以及其后的零至多个代表此操作所需参数(操作数).此外字节码指令是面向操作数栈的,这里操作数栈在功能上对应实 ...

  8. JVM学习笔记——字节码指令

    JVM学习笔记——字节码指令 字节码 0与 1是计算机仅能识别的信号,经过0和1的不同组合产生了数字之上的操作.另外,通过不同的组合亦产生了各种字符.同样,可以通过不同的组合产生不同的机器指令.在不同 ...

  9. JVM 字节码指令手册 - 查看 Java 字节码

    JVM 字节码指令手册 - 查看 Java 字节码 jdk 进行的编译生成的 .class 是 16 进制数据文件,不利于学习分析.通过下命令 javap -c Demo.class > Dem ...

随机推荐

  1. 【算法拾遗(java描写叙述)】--- 选择排序(直接选择排序、堆排序)

    选择排序的基本思想 每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,知道所有记录排序完毕.主要有两种选择排序方法:直接选择排序(或称简单选择排序)和堆排序. 直接选择排序 ...

  2. 用 query 方法 获得xml 节点的值

    DECLARE @result xml SET @result='<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelo ...

  3. 公布项目到NPM

    修己安人,内圣外王 近期,在开发Node项目过程中遇到了须要类jQuery深拷贝对象的问题.去Github找了半天,并没有符合的,于是,自己决定写一个(mixin.js),然后推送到NPM(查看Npm ...

  4. [UVALive 6661 Equal Sum Sets] (dfs 或 dp)

    题意: 求从不超过 N 的正整数其中选取 K 个不同的数字,组成和为 S 的方法数. 1 <= N <= 20  1 <= K<= 10  1 <= S <= 15 ...

  5. oracle经常使用函数(2)

    1.TRIM([ { { LEADING | TRAILING | BOTH }[ trim_character ]| trim_character} FROM ]trim_source) 函数 參数 ...

  6. bzoj3998: [TJOI2015]弦论(SAM+dfs)

    3998: [TJOI2015]弦论 题目:传送门 题解: SAM的入门题目(很好的复习了SAM并加强Right集合的使用) 其实对于第K小的字符串直接从root开始一通DFS就好,因为son边是直接 ...

  7. C#读出文本文件内容,遍历数组筛选出 含有汉字对应的拼音字符

    情景描述:由于任务需要,现有一用户表数据,用户名 字段 在新增用户时,输入中文和拼音两种,先要区分同时含有中文和拼音字母的用户名.由于数据很多,可以通过一段代码完成查询: 前提:在阅读本文之前可以先了 ...

  8. BZOJ 4321 DP

    思路: 这个DP太神了- 完全没想到 http://blog.csdn.net/geotcbrl/article/details/49663401 //By SiriusRen #include &l ...

  9. KCP kcptun

    简介 KCP是一个快速可靠协议,能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%,且最大延迟降低三倍的传输效果.纯算法实现,并不负责底层协议(如UDP)的收发,需要使用者 ...

  10. marge into操作

    动机: 想在Oracle中用一条SQL语句直接进行Insert/Update的操作. 说明: 在进行SQL语句编写时,我们经常会遇到大量的同时进行Insert/Update的语句 ,也就是说当存在记录 ...