`
lijingyao8206
  • 浏览: 219298 次
  • 性别: 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中文指令手册.pdf

    JVM指令手册详细记录了JVM的所有操作码(opcode),也就是字节码指令。这些指令是给JVM解释器或者即时编译器(JIT)使用的低级指令集。在JVM上运行的Java程序会被编译成一系列指令,然后由JVM执行。 从给定文件的...

    JVM指令集.zip

    Java虚拟机(JVM)是Java程序运行的基础,它通过解释和执行字节码来实现跨平台的特性。JVM指令集是JVM的核心组成部分,它定义了JVM能够理解和执行的一系列低级操作指令。这些指令构成了Java程序在运行时的微观世界,...

    JVM指令集.pdf

    JVM指令集的助记符通常由操作码对应的英文缩写构成,它们是字节码指令的符号化表示。 字节码是JVM能够识别和执行的一系列指令和数据,是Java平台无关性的基础,因为它允许Java程序在不同的操作系统上运行。字节码...

    JVM指令集 PDF 下载

    JVM指令集,也称为字节码指令集,是一系列二进制编码的指令,每个指令都对应一个特定的操作。这些指令在Java源代码被编译成.class文件时生成,每个类文件包含了一个方法区、一个堆、一个栈以及若干个本地方法栈。...

    JVM学习-字节码指令集(三)代码

    JVM学习-字节码指令集(三)代码

    JVM指令查询手册.pdf

    Java虚拟机(JVM)是Java程序运行的核心组件,它负责解释和执行Java字节码。JVM指令集是JVM内部的工作语言,由一系列单字节的指令组成,每条指令都有特定的功能。这份“JVM指令查询手册”很可能包含了JVM的所有公共...

    JVM指令集.docx

    Java虚拟机(JVM)指令集是Java编程语言在运行时执行的基本操作单元,它构成了Java字节码的基础。这些指令允许JVM执行程序并管理内存。以下是对JVM指令集的一些关键点的详细说明: 1. **常量压入栈的指令**:这些...

    jvm指令集.docx

    ### JVM指令集详解 ...这些指令在JVM执行Java字节码时起着至关重要的作用,确保程序能够正确地运行和管理内存。通过对这些指令的理解,我们可以更好地掌握JVM的工作原理及其在内存管理方面的机制。

    Java 字节码简单说明.zip

    1. 操作数栈:字节码指令执行时会操作一个动态栈,用于存放计算过程中的中间结果。 2. 局部变量表:每个方法都有一个局部变量表,用于存储方法参数和局部变量。 3. 方法区:存储类的元数据,如类名、方法信息等。 4....

    JVM指令手册.rar

    Java虚拟机(JVM)是Java程序运行的基础,它负责解释和执行Java字节码,为程序员提供了跨平台的运行环境。JVM指令手册是理解JVM内部工作原理的重要参考资料,其中包含了JVM执行的所有操作指令,这些指令是构建和优化...

    jvm-java字节码规则.pdf

    Java源代码在编译成.class文件时,会被编译器转换成JVM能够理解的字节码指令集。 Java字节码指令由一个字节的操作码(opcode)和多个字节的操作数(operand)组成。操作码指示了要执行的操作类型,而操作数则提供了...

    JAVA文件编译执行与虚拟机(JVM)介绍

    - **指令集与CPU架构**:JVM指令集是一组定义好的操作码,用于描述JVM如何执行字节码。虽然这些指令集是独立于具体CPU架构的,但JVM的实现通常会考虑目标CPU架构的特性来提高执行效率。 - **指令集的组成结构**:JVM...

    HelloWorld的javap -verbose HelloWorld 字节码初探

    JVM指令集是Java字节码的具体实现,每一行字节码对应一个或多个JVM指令,这些指令控制着JVM的运行流程。了解JVM指令有助于深入理解Java程序的执行机制,例如内存管理、方法调用、运算操作等。 结合以上信息,我们...

    轻松看懂Java字节码.pdf

    在实际分析Java字节码时,可以利用javap工具查看生成的字节码指令,例如以Main.class文件为例,会看到一系列的数字和字符组合。它们按照一定的格式描述了Java程序在执行时所需要的具体操作。文档中提到的内容部分...

    java字节码编辑器

    使用这样的工具需要具备一定的技术背景,包括Java虚拟机原理、字节码指令集以及可能的辅助工具,如JDK的`javap`命令用于查看字节码,或`jad`工具进行反编译。 总之,Java字节码编辑器是一个强大的工具,它为开发者...

    Java字节码指令集的使用详细

    Java字节码指令集是Java虚拟机(JVM)执行程序的基础,它是Java源代码经过编译后的二进制表示形式。每个字节码指令都由一个操作码(Opcode)和可能的操作数组成,用于控制JVM执行各种操作。本文将深入探讨Java字节码...

    字节码编程和操作系统等知识文档

    ASM是一个非常底层的字节码操作框架,它提供了直接操作字节码指令的API,因此使用ASM需要深入了解Java虚拟机规范和字节码指令集。ASM的操作比较接近于底层,因此性能较高,但是也相对复杂。Javassist提供了更高级的...

    java字节码指令集.docx

    Java字节码指令集是Java虚拟机(JVM)执行程序的基本单位,它构成了Java类文件的二进制表示形式。这些指令集控制了JVM如何解析和执行Java代码。在JVM中,程序的执行基于堆栈模型,其中每个线程都有自己的程序计数器...

    JVM指令手册.docx

    Java虚拟机(JVM)是Java程序运行的核心组件,它负责解释执行字节码指令,为Java应用程序提供了一个跨平台的运行环境。JVM指令集是JVM内部使用的微指令集合,这些指令构成了Java字节码的基础。在《JVM指令手册》中,...

Global site tag (gtag.js) - Google Analytics