字节码指令简介1字节码与数据类型2加载和存储指令3运算指令4类型转换指令5对象创建与访问指令6操作数栈管理指令7 控制转移指令8方法调用和返回指令9异常处理指令10同步指令公有设计和私有实现Class 文件结构的发展

本节内容作为了解即可,知道虚拟机字节码是怎么回事,有哪些类型的字节码,具体的字节码无需一一记住,正常的开发工作不会涉及到阅读字节码的内容。

字节码指令简介

  java虚拟机的指令由一个字节长度的、代表特殊操作含义的数组以及跟随其后的零至多个代表此项操作所需参数而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的架构,所以大多数指令都不包含操作数,只有一个操作码。 
  由于限制了java虚拟机操作码长度为一个字节,所以指令的操作码总数不可能超过256条; 
       由于Class文件格式放弃了编译后代码的操作数长度对齐,虚拟机处理那些长度超过一个字节数据的时候,在运行时从字节中重建出具体数据结构,如果要将一个16为长度的无符号证书使用两个无符号字节存储起来,(byte1 << 8) | byte2。 
  这种操作在某种程度上会导致解释执行字节码时损失一些性能。但优势也很明显,放弃操作对齐可以省略很多填充和间隔符号,用一个字节代表操作码也是为了尽可能获得短小精干的编译代码。

1字节码与数据类型

  Java虚拟机的指令集中,大多数指令都包含了其操作所对应的数据类型信息,如iload用于从局部变量表中加载int型的数据到操作数栈中,而fload指令加载的则是float类型的数据。这两段操作指令的操作在虚拟机内部可能使用同一段代码来实现,但是在class文件中他们需要由各自独立的操作码。 
       对于大多数与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表示为哪种数据类型服务:i代表int型,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。由一些助记符没有明确地指明操作类型的字母,如arraylength指令,操作数永远只能是一个数组类型的对象,还有一些如goto指令,则与数据类型无关的。 
  java虚拟机的操作码长度只有一个字节,因此无法将每一种数据类型相关的指令都支持java虚拟机运行时数据类型,有一些单独指令可以在必要的时候用来将一些不支持的数据类型转换为可被支持的。

  从上图可以看出,大部分指令都没有支持证书类型byte、char和short,甚至没有支持boolean类型的指令。编译器会在编译期或者运行期将byte和short类型的数据带符号扩展为相应的int型数据,将boolean和char类型数据零位扩展位相应的int型数据。

2加载和存储指令

  加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类执行包含如下内容: 
■将一个局部变量表加载到操作栈:Tload、Tload_其中T代表各种具体类型,可参见下表 
■将一个数值从操作栈存储到局部变量表:Tstore、Tstore_ 
■将一个常量加载到操作数栈:bipush、sipush 、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_、lconst_、fconst_、dcons_ 
■扩充局部变量表访问索引的指令:wide
存储数据的操作数栈和局部变量表主要就是由加载和存储指令进行操作,除此之外,还有少量指令,如访问对象的字段或数据元素的指令也会向操作数栈传输数据。 
       带有尖括号结尾的是一组指令,都是带有一个操作数的特殊形式,这种指令省略掉了显示的操作数,不需要进行取数动作,例如,iload_0与0的iload指令语义完全一致。

3运算指令

  运算指令用于将操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。大体上运算指令分为两种,1.对整型数据的运算与。2浮点型数据的运算。无论哪种都使用java虚拟机的数据类型,由于没有直接支持,byte,short,cahr,boolean类型的算数指令,对这些类型的数据运算用操作int型的指令代替。 
■加法指令:iadd、ladd、fadd、dadd
■减法指令:isub、lsub、fsub、dsub
■乘法指令:imul、lmul、fmul、dmul
■除法指令:idiv、ldiv、fdiv、ddiv
■求与指令:irem、lrem、frem、drem
■取反指令:ineg、lneg、fneg、dneg
■位移指令:ishl、ishr、iushr、lshl、lshr、lushr
■按位或指令:ior、lor
■按位与指令:iand、land
■按位异或指令:ixor、lxor
■局部变量自增指令:iinc
■比较指令:dcmpg/dcmpl、fcmpg、fcmpl、lcmp

  Java虚拟机规定,只有除法以及求与指令中当出现除数为0时会导致虚拟机抛出异常,其余任何场景都不应该抛出运行时异常,也就有了两个很大正整数相加结果可能是一个负数,但是并没有明确定义溢出数据的具体结果。 
  在处理浮点数时,有非正规浮点数值的运算规则和逐级下溢的运算规则。这些特征会使运算处理变得相对容易些。 
  java虚拟机要求在进行浮点数运算时,所有的运算结果都要舍入到一定的精度,非精确结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,则选择最低有效位为零的。 
  把浮点数转换为正数会使用向零舍入模式,会导致数字被截断,所有小数部分都会被丢弃。 
  当一个浮点数操作产生溢出时将使用一个有符号无穷大表示,某个结果没有明确的数学定义,将使用NaN来表示。 
  对long型数据比较时,虚拟机使用带符号的比较方式,而对浮点数值进行比较时(dcmpg、dcmpl、fcmpg、fcmpl),虚拟机会采用无信号(Nonaifnling Comparisons)比较方式。

4类型转换指令

  类型转换指令可以将两种不同的数值类型进行相互转换,一般用于用户代码中的显式类型转换操作,或者用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。 宽化: 
  Java 虚拟机直接支持(即转换时无需显式的转换指令)以下数值类型的宽化类型转换(Widening Numeric Conversions,即小范围类型向大范围类型的安全转换): 
■int类型到long、float 或者 double类型。 
■long 类型到 float、double 类型。 
■float 类型到 double类型。

窄化: 
  相对的,处理窄化类型转换(Narrowing Numeric Conversions)时,必须显式地使用转换指令来完成,这些转换指令包括:i2b、i2c、i2s、12i、f2i、f21、d2i、d21和d2f.窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级的情况,转换过程很可能会导致数值的精度丢失。

  在将int或long类型窄化转换为整数类型T的时候,转换过程仅仅是简单地丢弃除最低位N个字节以外的内容,N是类型T的数据类型长度,这将可能导致转换结果与输入值有不同的正负号。这点很容易理解,因为原来符号位处于数值的最高位,高位被丢弃之后,转换结果的符号就取决于低N个字节的首位了。 
      在将一个浮点值窄化转换为整数类型T(T限于int或long类型之一)的时候,将遵循以下转换规则: 
■如果浮点值是NaN,那转换结果就是int或long类型的0. 
■如果浮点值不是无穷大的话,浮点值使用 IEEE 754的向零舍入模式取整,获得整数值v,如果v在目标类型T(int或long)的表示范围之内,那转换结果就是V. 
■否则,将根据v的符号,转换为T所能表示的最大或者最小正数。 
  从double类型到float类型的窄化转换过程与IEEE 754中定义的一致,通过 IEEE 754向最接近数舍入模式舍入得到一个可以使用float类型表示的数字。如果转换结果的绝对值太小而无法使用float来表示的话,将返回 float类型的正负零。如果转换结果的绝对值太大而无法使用float来表示的话,将返回float类型的正负无穷大,对于double 类型的NaN值将按规定转换为 float类型的NaN值。 
  尽管数据类型窄化转换可能会发生上限溢出、下限溢出和精度丢失等情况,但是Java虚拟机规范中明确规定数值类型的窄化转换指令永远不可能导致虚拟机抛出运行时异常。

5对象创建与访问指令

  虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。对象创建后,就可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素,这些指令如下。 
■创建类实例的指令:new.
■创建数组的指令:newarray、anewarray、multianewarray.
■访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getfield、putfield、getstatic、putstatic.
■把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload.
■将一个操作数栈的值存储到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore.
■取数组长度的指令:arraylength.
■检查类实例类型的指令:instanceof、checkcast.

6操作数栈管理指令

  如同操作一个普通数据结构中的堆栈那样,Java虚拟机提供了一些用于直接操作操作数栈的指令,包括: 
■将操作数栈的栈顶一个或两个元素出栈:pop、pop2.
■复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2.
■将栈最顶端的两个数值互换:swap.

7 控制转移指令

  控制转移指令可以让Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序,从概念模型上理解,可以认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。控制转移指令如下。 
■条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq 和if_acmpne.
■复合条件分支:tableswitch、lookupswitch.
■无条件分支:goto、goto_w、jsr、jsr_w、ret.

  在Java虚拟机中有专门的指令集用来处理int和reference类型的条件分支比较操作,为了可以无须明显标识一个实体值是否null,也有专门的指令用来检测 null值。 
对于boolean类型、byte类型、char类型和short类型的条件分支比较操作,都是使用int类型的比较指令来完成,而对于long类型、float类型和double类型的条件分支比较操作,则会先执行相应类型的比较运算指令(dcmpg、dcmpl、 fcmpg、fcmpl、lcmp),运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件分支比较操作来完成整个分支跳转。由于各种类型的比较最终都会转化为int类型的比较操作,int类型比较是否方便完善就显得尤为重要,所以Java 虚拟机提供的int类型的条件分支指令是最为丰富和强大的。

8方法调用和返回指令

  方法调用(分派、执行过程)将在后边具体讲解,这里仅列举以下5条用于方法调用的指令。 
■invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。 
■invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。 
■invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。 
■invokestatic 指令用于调用类方法(static 方法)。 
■invokedynamic 指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面4条调用指令的分派逻辑都固化在Java虚拟机内部,而invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的。

  方法调用指令与数据类型无关,而方法返回指令是根据返回值的类型区分的,包括ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条 return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。

9异常处理指令

  在Java程序中显式抛出异常的操作(throw语句)都由athrow指令来实现,除了用throw语句显式抛出异常情况之外,Java虚拟机规范还规定了许多运行时异常会在其他 Java虚拟机指令检测到异常状况时自动抛出。例如,在前面介绍的整数运算中,当除数为零时,虚拟机会在idiv或ldiv指令中抛出 ArithmeticException 异常。而在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和ret指令来实现,现在已经不用了),而是采用异常表来完成的。

10同步指令

  Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor)来支持的。方法级的同步是隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED 访问标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的ACCSYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个同步方法所持有的管程将在异常抛到同步方法之外时自动释放。 
  同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义,正确实现 synchronized 关键字需要 Javac编译器与Java虚拟机两者共同协作支持,譬如代码清单6-6中所示的代码。

public class SyncDemo {

void Sync(SyncDemo s) {

synchronized (s) {

doSth();

}

}

void doSth(){

}

}

  编译器必须确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。 
     从代码清单的字节码序列中可以看到,为了保证在方法异常完成时 monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。

公有设计和私有实现

  Java 虚拟机规范描绘了 Java 虚拟机应有的共同程序存储格式:Class 文件格式以及字节码指令集。这些内容与硬件、操作系统及具体的Java虚拟机实现之间是完全独立的,虚拟机实现者可能更愿意把它们看做是程序在各种Java平台实现之间互相安全地交互的手段。 
  Java 虚拟机实现必须能够读取Class 文件并精确实现包含在其中的Java虚拟机代码的语义。一个优秀的虚拟机实现,在满足虚拟机规范的约束下对具体实现做出修改和优化也是完全可行的。只要优化后Class文件依然可以被正确读取,并且包含在其中的语义能得到完整的保持,那实现者就可以选择任何方式去实现这些语义,只要它在外部接口上看起来与规范描述的一致即可。 
  虚拟机实现者可以使用这种伸缩性来让Java虚拟机获得更高的性能、更低的内存消耗或者更好的可移植性。虚拟机实现的方式主要有以下两种: 
■将输入的 Java 虚拟机代码在加载或执行时翻译成另外一种虚拟机的指令集。 
■将输入的 Java虚拟机代码在加载或执行时翻译成宿主机CPU的本地指令集(即JIT代码生成技术)。

Class 文件结构的发展

  Class 文件结构自 Java 虚拟机规范第1版订立以来,已经有二十多年的历史。这二十多年间,Java技术体系有了翻天覆地的改变,JDK的版本号已经从1.0提升到了15.相对于语言、API以及Java技术体系中其他方面的变化,Class文件结构一直处于比较稳定的状态,Class文件的主体结构、字节码指令的语义和数量几乎没有出现过变动。

c++byte数组和文件的相互转换_5分钟系列之Java类文件结构(三、字节码指令简介)...相关推荐

  1. jvm(6)-java类文件结构(字节码文件)

    [0]README 0.1)本文部分文字描述转自 "深入理解jvm",旨在学习类文件结构  的基础知识: 0.2)本文荔枝以及荔枝的分析均为原创: 0.3)下面的截图中有附注t*编 ...

  2. java文件解析器_jvm:java类文件结构(字节码文件的解析)

    1.java虚拟机简介 不是只有java编译器才能完成java程序到字节码的编译过程 (2)定义 java二进制字节码的运行环境 (3)好处 一次编写,到处运行的基础 自动内存管理,垃圾回收功能,大大 ...

  3. c++byte数组和文件的相互转换_终于!word、excel、ppt文件相互转换技巧来了!

    不知道大家在用Office软件的时候,有没有遇到过这样的问题.做了个Word文档总结,却需要转换成PPT上台汇报:做了个Word表格统计,却需要转换成Excel进行分析:做了个Excel图表分析,却需 ...

  4. c++byte数组和文件的相互转换_经常对文件相互转换,全能转换工具,解决办公中遇到的所有难题...

    迅捷PDF转换器是一款功能强大.界面简洁.操作简单的PDF转换成Word或word转换成pdf的软件.软件具备快速转换.批量转换,高质量识别等功能.你只需把PDF文件拖拽到迅捷PDF转换器软件界面中, ...

  5. JavaScript将后端获取到的byte数组转为文件

    前言 一般情况下,在我们写项目的时候,都会从后端获取到文件的数据,要么是base64,要么是byte数组,然后我们再通过拿到的数据额外做出来转换为文件的,那么这次教大家如何通过JavaScript将后 ...

  6. 应用程序文件Android安全分析挑战:运行时篡改Dalvik字节码

    发一下牢骚和主题无关: 本文章由Jack_Jia编写,转载请注明出处. 文章接链:http://blog.csdn.net/jiazhijun/article/details/8833710 作者:J ...

  7. java编译后生成字节码_请问java源文件编译后怎么生成字节码文件?

    比如,有的java源程序生成一个字节码文件,带有内部类的生成两个.可是有一种情况怎么回事呢?importjava.awt.*;importjavax.swing.*;importjava.awt.ev ...

  8. Android中怎样使用MediaPlayer播放byte数组音频文件

    场景 在得到某音频文件的byte[]后使用MediaPlayer将其播放出来. 注: 博客: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸道的程序 ...

  9. Iphone开发-NSdata 与 NSString,Byte数组,UIImage 的相互转换

    原文摘自:NSdata转化为int 1. NSData 与 NSString NSData-> NSString NSString *aString = [[NSString alloc] in ...

最新文章

  1. Git多个commit合并成一个【中间提交合并 尾部提交合并】
  2. 感知算法论文(九):Towards Accurate One-Stage Object Detection with AP-Loss
  3. 空间谱专题16:信号个数估计
  4. SpringCloud学习笔记006---使用properties配置文件实现多环境配置
  5. python类型提示模块包_Python checktypes包_程序模块 - PyPI - Python中文网
  6. 微软MSDN提供的Visual Studio开发文档
  7. 我们差点就用不上 Java 了!
  8. 目标跟踪学习笔记_2(particle filter初探1)
  9. 正确调用腾讯x5内核详解
  10. 4WRLE27Q3-600M-4X/MXY/24A1比例先导方向阀
  11. Junos CLI常用命令
  12. 如何用ESP8266 向手机App 发送信息
  13. 软件测试工程师需要学什么?全网最全,测试工程师技能树,吐血整理
  14. linux服务器console口,Linux重定向console口控制台
  15. iOS - MFi 认证
  16. java计算机毕业设计基于安卓Android/微信小程序的婚恋交友系统uni-app
  17. java通过API给企业微信用户发送微信消息
  18. Node js 开发之Postman
  19. 香港监管机构收紧加密货币相关法规
  20. 如果想赚钱,先学理财吧,那么入门书籍有哪些?富爸爸穷爸爸 名副其实NO1

热门文章

  1. php原生读取excel文件夹,原生php实现excel文件读写的方法分析php技巧
  2. java swing 图片容器_Java Swing中两种设置背景图(容器图片)
  3. Centos7.6 编译安装heartbeat,及遇到的问题。
  4. android常见布局整理
  5. centos7 yum安装mysql5_Centos7 yum安装mysql5.7
  6. Ssm框架实现的学生信息管理系统
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的医院在线预约挂号系统
  8. Linux基础命令---查找进程id
  9. (转)【MySQL】sync_binlog innodb_flush_log_at_trx_commit 浅析
  10. eclipse设置工作空间编码为默认utf-8