`
lijingyao8206
  • 浏览: 216627 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
社区版块
存档分类
最新评论

JVM字节码执行模型及字节码指令集

阅读更多

    一个Java类的生命周期概括来说需要经过加载、验证、准备、解析以及初始化、使用及卸载的过程。这里不展开加载Class 的过程以及Class文件格式(后期会陆续探讨)。在执行过程中,JVM是如何把Class文件里的字节码转换成我们的虚拟机栈的操作指令,以及整个虚拟机栈的内部数据结构是怎样的,这篇文章后续会详细介绍,并且稍微扩展下JVM规范中的一些字节码指令集。

    其实这篇文章的主要目的还是为了引入后续要介绍的ASM框架的CoreApi 中的Method接口和组件来做一个铺垫。我们知道,Class文件是编译后的以8byte为单位存储的二进制字节流,想要生成和解析一个Class文件,那么我们需要更好地了解在JVM中他是怎样被解析和执行的。整篇主要参考和总结了《Java Virtual Machine SpecificationJavaSE7 Version》以及《ASM 4.0

A Java bytecode engineering library》关于虚拟机执行模型及字节码执行的部分。

一、字节码执行

    方法调用在JVM中转换成的是字节码执行,字节码指令执行的数据结构就是栈帧(stack frame)。也就是在虚拟机栈中的栈元素。虚拟机会为每个方法分配一个栈帧,因为虚拟机栈是LIFO(后进先出)的,所以当前线程正在活动的栈帧,也就是栈顶的栈帧,JVM规范中称之为“CurrentFrame”,这个当前栈帧对应的方法就是“CurrentMethod”。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作。

栈帧的数据结构主要分为四个部分:局部变量表、操作数栈、动态链接以及方法返回地址(包括正常调用和异常调用的完成结果)。下面就一一介绍下这四种数据结构。

 1、局部变量表(local variables)

    当方法被调用时,参数会传递到从0开始的连续的局部变量表的索引位置上。栈帧中局部变量表的长度存储在类或接口的二进制表示中。阅读Class文件会找到Code属性,所以我们能知道local variables的最大长度是在编译期间决定的。一个局部变量表的占用了32位的存储空间(一个存储单位称之为slot,槽),所以可以存储一个boolean、byte、char、short、float、int、refrence和returnAdress数据,long和double需要2个连续的局部变量表来保存,通过较小位置的索引来获取。如果被调用的是实例方法,那么第0个位置存储“this”关键字代表当前实例对象的引用。

 

2、操作数栈(operand stack)

    操作数栈同局部变量表一样,也是编译期间就能决定了其存储空间(最大的单位长度),通过 Code属性存储在类或接口的字节流中。操作数栈也是个LIFO栈。

操作数栈是在JVM字节码执行一些指令(第二部分会介绍一些指令集)时创建的,主要是把局部变量表中的变量压入操作数栈,在操作数栈中进行字节码指令的操作,再将变量出操作数栈,结果入操作数栈。同局部变量表,除了long和double,其他类型数据都只占用一个栈的单位深度。

3、动态链接

    每个栈帧指向运行时常量池中该栈帧所属的方法的引用,也就是字节码的发放调用的引用。动态链接就是将符号引用所表示的方法,转换成方法的直接引用。加载阶段或第一次使用时转化为直接引用的(将变量的访问转化为访问这些变量的存储结构所在的运行时内存位置)就叫做静态解析。JVM的动态链接还支持运行期转化为直接引用。也可以叫做Late Binding,晚期绑定。

4、方法返回地址

    方法正常退出会把返回值压入调用者的栈帧的操作数栈,PC计数器的值就会调整到方法调用指令后面的一条指令。这样使得当前的栈帧能够和调用者连接起来,并且让调用者的栈帧的操作数栈继续往下执行。

方法的异常调用完成,主要是JVM跑出的异常,如果异常没有被不货主,或者遇到athrow字节码指令显示抛出,那么就没有返回值给调用者。

 

二、字节码指令集

    了解了栈帧的数据结构之后,继续扩展到字节码指令集的扩展。那么字节码指令又是由哪些元素构成,以及会怎样地影响我们的当前方法的栈帧的出栈、入栈操作的呢。从结构到用途开始详述一部分指令集(主要是从作用范围将指令集划分为两类:局部变量表和操作数栈传递数据的指令集,只在操作数栈中操作的指令集)。

1、构成元素

    字节码的指令,是由一个字节长度的助记符表示的操作码(Opcode)以及其随后的需要操作的若干参数构成。有的指令并不一定需要参数。但这里注意不要混淆一个概念,这里的参数和操作数(oprends)不是同一个概念。这里的arguments(参数)是静态的值,编译期就存储在编译后的字节码中,而Oprends(操作数)的值第一节介绍的操作数栈中运行期才知道值的数据结构。不知道讲清楚没有,但发现很多译文以及文章都会混淆指令集的”参数”和操作数栈的”操作数”。如果这里不够清晰,那么下面继续看,后面具体的指令集的例子,就清楚了。

    对于操作参数的数量及长度都是由Opcode决定的,如果需要操作的长度超出了一个字节,就会按照高位在前的字节序存储。并且字节码指令流都是单字节对齐,所以超出单字节的操作参数会需要预留“位置”来实现对齐。

     这里还需要我们记住的一点是,Opcode是由一个字节长度的助记符表示,JVM 规范制定中需要很谨慎小心得“节约”指令的命名。对于一些boolean、short、byte、char的操作都是讲数据转化成int数据进行操作的,这样就可以使用同一条指令来操作更多的数据类型。下面可以看到一些指令的例子。

2、指令

    按照JVM规范中,将字节码指令按照用途划分成加载和存储指令、运算指令、类型转换指令、对象创建指令、操作数栈管理指令、方法调用和返回指令以及同步指令等等。看起来颇多。这里我们按照指令的操作范围划分为两种:局部变量表和操作数栈传递数据的指令以及只在操作数栈中操作的指令。

本文不打算详细把所有字节码指令全部列出来,所有的指令及规范可以阅读《Java Virtual Machine Specification》。因为我们先要概念上了解一个字节码指令是如何在JVM栈帧上操作的。

这里先简单列举一下Class文件中对于Java的类型描述。以下是Java基础类型和数组、Object的表述。对于类或者接口,类型描述其实是将如java.lang.String 变成了java/lang/String 。用斜线分隔。

    编译后的方法描述对应关系如下:

 

 

 

1、局部变量表和操作数栈传递数据的指令:

    ILOAD, LLOAD, FLOAD, DLOAD以及ALOAD指令都是从局部变量表中获取参数压入到操作数栈的,其中ILOAD包括了load boolean、char、short、byte和int类型的操作。FLOAD, DLOAD 指令操作的数据需要占用两个槽(slot i 及i+1)。ALOAD 是load 对象或者数组类型。ISTORE,LSTORE,ASTORE等操作是从操作数栈栈顶压入局部变量表的指令。

2、只在操作数栈操作数据的指令:

    2.1 栈操作:POP 指令把值压到栈顶。还有DUP、SWAP指令

    2.2 常量值推入栈顶:ACONST_NULL 把null值推入,ICONST_0 把int 0推入栈顶,其他指令不一一列举了

    2.3运算操作:xADD, xSUB, xMUL, xDIV 以及 xREM。对应着+,-,*,/ ,%的运算。X分别对应前面提到的基本数据类型。

    2.4 类型转换:I2F, F2D, L2D 等等是对类型转换的操作。

    2.5 对象操作:如NEW 指令就将一个对象引用入栈。

    2.6 读写Fields:GETFIELD,PUTFIELD。对于static属性的操作有:GETSTATIC ,PUTSTATIC

    2.7 调用Methods:对方法的调用,构造函数操作的时候,会操作所有方法参数入栈。如INVOKESTATIC、INVOKEINTERFACE等。

    2.8 读写数组值:xALOAD以及xASTORE 。x对应的是I, L, F, D ,A, B, C , S等类型数据的数组的索引、值入栈出栈的操作。

    2.9 跳转操作:TABLESWITCH、LOOKUPSWITCH 指令对应的是switch的操作指令。作为条件判断if、do while、continue 等的跳转指令也是直接在操作数栈中进行的。

    2.10 返回指令:RETURN 以及xRETURN、前者是对应方法返回void类型的操作,后者是对应x类型的返回值,返回给方法调用者的指令。

 

三、例子

    下面来结合例子来看下字节码指令在虚拟机栈中的操作,更进一步理解部分字节码指令的含义。

package bytecode;

/**
 * Created by yunshen.ljy on 2015/6/16.
 */
public class Coffee {

    int bean;

    public int getBean() {
        return this.bean;
    }

    public void setBean(int bean) {
        this.bean = bean;
    }

}

    然后查看字节码:

1、getBean 方法如下:
   0:	aload_0
   1:	getfield	#2; //Field bean:I
   4:	ireturn

 

    第一行指令是当方法被调用,也就是方法的栈帧创建时,将获取局部变量表索引值为0 的值(也就是this),入操作数栈。

   第二行指令,将这个值(也就是this对应的值)出栈,赋给this对象的 bean field。

   第三行指令,将this.f 出栈,并且将值返回给调用者(这里ireturn 是int类型)。

 

2、setBean() 方法字节码:
   0:	aload_0
   1:	iload_1
   2:	putfield	#2; //Field bean:I
   5:	return

    第一行指令和getBean 方法一样,都是将this入操作数栈。

    第二行指令是将已经初始化(栈帧创建,也就是方法调用时初始化)的参数bean 的值入操作数栈。

    第三行指令将这两个值出栈,并且存储这个int值存储到到bean属性的引用,也就是this.bean中。

    第四行指令,在源码中没有return 语句,但是在编译后的字节码中,会自动生成一个return 指令,消除当前方法(current method)的栈帧并且返回给调用者。

 

    当然,如果没有程序实现自己的构造器的话,编译后的类还有个默认的public 构造器。Coffee () { super(); }

3、构造器的字节码如下:

 

   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	return

    第一行指令和getBean 方法一样,都是将this如操作数栈。

    第二行指令,将值(this)出栈,并调用Object class的<init>方法,其实也就是因为隐式调用了super()方法,而Coffee的父类是Object。这里的<init>方法是编译后的类对应的构造器的方法,编译器会为每个构造器生成一个<init>方法。

    第三行指令,同之前的几个return命令一样,返回给调用者。

 

四、后话

    至此,我们了解了虚拟机执行模型,线程执行会独有一个方法栈,每个方法在调用时创建一个栈帧,当前正在执行的方法对应的栈帧是在栈顶,所有执行的分析都是针对当前方法进行的。后续又了解了部分字节码指令,结合例子分析了指令集是如何对栈帧进行操作的。如果想更深入地了解虚拟机可以看看《深入理解JVM》,《JVM规范》,《EffectiveJava》都是不错的参考资料。后续会继续写一下Class 文件结构,JVM中的类加载过程,GC收集及编译、运行时调优等方面的内容。

  • 大小: 75.7 KB
分享到:
评论

相关推荐

    JVM执行子系统原理

    详细介绍了JVM执行子系统的工作原理,包括类文件结构与字节码指令(Class类文件结构、JVM字节码指令简介)、JVM类加载机制(类加载器、类加载时机、类加载过程)、字节码执行引擎(运行时候的栈结构、方法调用、方法...

    第2章:字节码指令集与解析举例.mmap

    第2章:字节码指令集与解析举例.mmap

    简单实用JVM参数配置

    Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上...

    JVM指令集(1).docx

    jvm常用的指令,是分析字节码反汇编的必备指令 常用的指令 &gt; iconst_0 将int类型常量0压入栈 &gt; istore_1 将int类型值存入局部变量1 &gt; iconst_0 将int类型常量0压入栈 &gt; istore_2 将int类型值存入局部变量2 &gt; iload_1...

    JVM指令集.pdf

    JVM指令助记符,对于java的字节码研究者是一份不错的选择,便于酷爱者学习。

    JVM简介以及历史.docx

    (2)jvm包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。 (3)JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台...

    详细讲解了jvm在java中应用

    特点:Java 虚拟机基于二进制字节码执行,由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆、一个方法区等组成;JVM 屏蔽了与操作系统平台相关的信息,从而能够让 Java 程序只需要生成能够在 JVM 上运行的...

    JVM+学习笔记资源合集

    JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。 JVM一直是java知识里面进阶阶段的重要部分,如果希望在java领域研究的更深入,则JVM则是如论如何也避开不了的话题,本系列试图通过...

    30道JVM综合面试题详解含答案(值得珍藏)

    JVM包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。 JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等...

    java字节码指令集.docx

    JVM简介 栈和局部变量操作 将常量压入栈的指令 aconst_null 将null对象引用压入栈 iconst_m1 将int类型常量-1压入栈 iconst_0 将int类型常量0压入栈 iconst_1 将int类型常量1压入栈 iconst_2 将int类型常量2压入栈 ...

    sunjava虚拟机(jvm)v1.6官方安装版java(TM)6Update4

    JVM:Java Virtual Machine(JAVA虚拟机)。JVM是JRE的一部分,它是一个...JVM 的主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 的指令集或 OS 的系统调用。Java语言是跨平台运行的,其实就是不同的操作系统

    Java虚拟机指令手册+复习面试题+从入门到进阶完整资源合集

    Java虚拟机在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。 本教程有一套多线程、JVM复习&面试&强化训练100题合集,面试更有信心,面试不再发愁。包含多线程面试60题、jvm面试40题详解,本...

    JVM执行子系统-JVM进阶

    各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode)是 构成平台无关性的基石,也是语言无关性的基础。Java 虚拟机不和包括 Java 在内的任何 语言绑定,它只与“Class 文件”这种特定的...

    JVM介绍

    Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不...

    阿里大佬总结的Java面试资料.pdf

    JVM 是可运行 Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接 的交互。 (2) 运行过程: 13/04/2018 Page...

    JAVA 运行环境安装包

    它是一个虚拟的计算机环境,具有自己的指令集和内存模型。它负责加载字节码文件,并在运行时进行解释或编译执行。Java虚拟机还提供了垃圾回收等机制,方便自动管理内存资源,提高程序的稳定性和安全性。 除了Java...

    Java理论与实践:在JDK早期版本中使用Java 5的语言特性

    本文介绍了Java 5中添加的语言特性--泛型、枚举、注释、自动装箱和增强的for循环--不需要修改JVM的指令集,几乎全部是在静态编译器和类库中实现的。对于不能使用Java 5语言特性的开发人员,有多种方法可以使他们使用...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 221 8.4.2 基于栈的指令集与基于寄存器的指令集 / 223 8.4.3 基于栈的解释器执行过程 / 224 8.5 本章小结 / 230 第9章 类加载及执行子系统的案例与实战 / 231 9.1 概述 / 231 9.2 案例分析 / 231 9.2.1 ...

    JVM执行子系统.docx

    各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode)是 构成平台无关性的基石,也是语言无关性的基础。 Java 虚拟机不和包括 Java 在内的任何 语言绑定,它只与“Class 文件”这种特定的二...

Global site tag (gtag.js) - Google Analytics