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

    Java虚拟机指令由一个字节长度的、代表某种特定含义的操作码(Opcode)以及其后的零个至多个代表此操作参数的操作数构成。虚拟机中许多指令并不包含操作数,只有一个操作码。若忽略异常,JVM解释器使用一下为代码即可有效工作。

复制代码 代码如下:

do{
    自动计算PC寄存器以及从PC寄存器的位置取出操作码
    if(存在操作数) 取出操作数;
    执行操作码所定义的操作;
}while(处理下一次循环)

操作数的数量以及长度,取决于操作码,若一个操作数长度超过了一个字节,将会以Big-Endian顺序存储(高位在前字节码),其值应为(byte1<<8)|byte2。

    字节码指令流是单字节对齐,只有"tableswitch"和"lookupswitch"两指令例外,它们的操作数比较特殊,以4字节为界限划分的,需要预留出相应的空位来实现对齐。

    限制Java虚拟机操作码的长度为一个字节,且放弃编译后代码的参数长度对齐,是为了获得短小精干的编译代码,即使可能会让JVM实现付出一定性能成本为代价。由于操作码只能有一个字节长度,故限制了指令集的数量,又没有假设数据是对齐好的,意味着数据超过一个字节时,不得不从字节中重建出具体的数据结构,会损失一些性能。

数据类型与Java虚拟机

    在JVM中的指令集中,大多数指令包含了其操作对应的数据类型信息。如iload指令从局部变量表中加载int型的数据到操作数栈中,而fload加载的是float类型的数据。

    对于大部分与数据类型相关的字节码指令,他们的操作码助记符都有特殊的字符来表明:i代表int类型,l代表long,s代表short,b代表 byte,c代表char,f代表float,d代表double,a代表reference。有一些单独指令可以在必要的时候用来将一些不不支持的类型转换为可被支持的类型。

加载和存储指令

    加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传输。
    1)将一个局部变量加载到操作数栈的指令包括:iload,iload_<n>,lload、lload_<n>、float、 fload_<n>、dload、dload_<n>,aload、aload_<n>。
    2)将一个数值从操作数栈存储到局部变量标的指令:istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
    3)将常量加载到操作数栈的指令:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>
    4)局部变量表的访问索引指令:wide
    一部分以尖括号结尾的指令代表了一组指令,如iload_<i>,代表了iload_0,iload_1等,这几组指令都是带有一个操作数的通用指令。

运算指令

    算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
    1)加法指令:iadd,ladd,fadd,dadd
    2)减法指令:isub,lsub,fsub,dsub
    3)乘法指令:imul,lmul,fmul,dmul
    4)除法指令:idiv,ldiv,fdiv,ddiv
    5)求余指令:irem,lrem,frem,drem
    6)取反指令:ineg,leng,fneg,dneg
    7)位移指令:ishl,ishr,iushr,lshl,lshr,lushr
    8)按位或指令:ior,lor
    9)按位与指令:iand,land
    10)按位异或指令:ixor,lxor
    11)局部变量自增指令:iinc
    12)比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

    Java虚拟机没有明确规定整型数据溢出的情况,但规定了处理整型数据时,只有除法和求余指令出现除数为0时会导致虚拟机抛出异常。

加载和存储指令

    加载和存储指令用于将数据从哦你哦过栈帧的局部变量表和操作数栈之间来回传输。

    1)将一个局部变量加载到操作数栈的指令包括:iload,iload_<n>,lload、lload_<n>、float、 fload_<n>、dload、dload_<n>,aload、aload_<n>。
    2)将一个数值从操作数栈存储到局部变量标的指令:istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
    3)将常量加载到操作数栈的指令:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>
    4)局部变量表的访问索引指令:wide
    一部分以尖括号结尾的指令代表了一组指令,如iload_<i>,代表了iload_0,iload_1等,这几组指令都是带有一个操作数的通用指令。

运算指令

    算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
    1)加法指令:iadd,ladd,fadd,dadd
    2)减法指令:isub,lsub,fsub,dsub
    3)乘法指令:imul,lmul,fmul,dmul
    4)除法指令:idiv,ldiv,fdiv,ddiv
    5)求余指令:irem,lrem,frem,drem
    6)取反指令:ineg,leng,fneg,dneg
    7)位移指令:ishl,ishr,iushr,lshl,lshr,lushr
    8)按位或指令:ior,lor
    9)按位与指令:iand,land
    10)按位异或指令:ixor,lxor
    11)局部变量自增指令:iinc
    12)比较指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

    Java虚拟机没有明确规定整型数据溢出的情况,但规定了处理整型数据时,只有除法和求余指令出现除数为0时会导致虚拟机抛出异常。

类型转换指令

    类型转换指令将两种Java虚拟机数值类型相互转换,这些操作一般用于实现用户代码的显式类型转换操作。
    JVM支持宽化类型转换(小范围类型向大范围类型转换):
    1)int类型到long,float,double类型
    2)long类型到float,double类型
    3)float到double类型

    窄花类型转换指令:i2b,i2c,i2s,l2i,f2i,f2l,d2l和d2f,窄化类型转换可能会导致转换结果产生不同的正负号,不同数量级,转换过程可能会导致数值丢失精度。如int或long类型转化整数类型T时,转换过程是仅仅丢弃最低位N个字节意外的内容(N是类型T的数据类型长度)

对象创建与操作

    虽然类实例和数组都是对象,Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。
    1)创建实例的指令:new
    2)创建数组的指令:newarray,anewarray,multianewarray
    3)访问字段指令:getfield,putfield,getstatic,putstatic
    4)把数组元素加载到操作数栈指令:baload,caload,saload,iaload,laload,faload,daload,aaload
    5)将操作数栈的数值存储到数组元素中执行:bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
    6)取数组长度指令:arraylength
    7)检查实例类型指令:instanceof,checkcast

操作数栈管理指令

    直接操作操作数栈的指令:pop,pop2,dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2和swap
控制转移指令

    让JVM有条件或无条件从指定指令而不是控制转移指令的下一条指令继续执行程序。控制转移指令包括:
    1)条件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt等

    2)复合条件分支:tableswitch,lookupswitch

    3)无条件分支:goto,goto_w,jsr,jsr_w,ret

    JVM中有专门的指令集处理int和reference类型的条件分支比较操作,为了可以无明显标示一个实体值是否是null,有专门的指令检测null 值。boolean类型和byte类型,char类型和short类型的条件分支比较操作,都使用int类型的比较指令完成,而 long,float,double条件分支比较操作,由相应类型的比较运算指令,运算指令会返回一个整型值到操作数栈中,随后再执行int类型的条件比较操作完成整个分支跳转。各种类型的比较都最终会转化为int类型的比较操作。

方法调用和返回指令

    invokevirtual指令:调用对象的实例方法,根据对象的实际类型进行分派(虚拟机分派)。
    invokeinterface指令:调用接口方法,在运行时搜索一个实现这个接口方法的对象,找出合适的方法进行调用。
    invokespecial:调用需要特殊处理的实例方法,包括实例初始化方法,私有方法和父类方法
    invokestatic:调用类方法(static)
    方法返回指令是根据返回值的类型区分的,包括ireturn(返回值是boolean,byte,char,short和 int),lreturn,freturn,drturn和areturn,另外一个return供void方法,实例初始化方法,类和接口的类初始化i 方法使用。

同步

    JVM支持方法级同步和方法内部一段指令序列同步,这两种都是通过moniter实现的。

    方法级的同步是隐式的,无需通过字节码指令来控制,它实现在方法调用和返回操作中。虚拟机从方法常量池中的方法标结构中的 ACC_SYNCHRONIZED标志区分是否是同步方法。方法调用时,调用指令会检查该标志是否被设置,若设置,执行线程持有moniter,然后执行方法,最后完成方法时释放moniter。

    同步一段指令集序列,通常由synchronized块标示,JVM指令集中有monitorenter和monitorexit来支持synchronized语义。

    结构化锁定是指方法调用期间每一个monitor退出都与前面monitor进入相匹配的情形。JVM通过以下两条规则来保证结结构化锁成立(T代表一线程,M代表一个monitor):

    1)T在方法执行时持有M的次数必须与T在方法完成时释放的M次数相等

    2)任何时刻都不会出现T释放M的次数比T持有M的次数多的情况

时间: 2024-08-03 12:05:33

Java字节码指令集的使用详细_java的相关文章

通过java字节码分析学习对象初始化顺序_java

复制代码 代码如下: mockery.checking(new Expectations() {            {               one(new Object()).toString();               will(returnValue(""));           }       }); 下面写一个写一个简单的类演示这个例子 复制代码 代码如下: public class Test {     int i = 1;    {        int

Java字节码(.class文件)格式详解(一)

小介:去年在读<深入解析JVM>的时候写的,记得当时还想着用自己的代码解析字节码的,最后只完成了一部分.现在都不知道还有没有保留着,貌似Apache有现成的BCEL工程可以做这件事.当时也只是为了学习.这份资料主要参考<深入解析JVM>和<Java虚拟机规范>貌似是1.2版本的,整理出来的.里面包含了一些自己的理解和用实际代码的测试.有兴趣的童鞋可以研究研究.嘿嘿.要有错误也希望能为小弟指点出来,感激不尽.:) 1.总体格式 Class File format type

Java字节码

原文出处:https://www.ibm.com/developerworks/library/it-haggar_bytecode/index.html#opcode 作者:Peter Haggar 发表时间:2001 / 07 / 01 理解字节码可以使你变成一个更好的程序员 关于字节码的信息,以及这里提供的字节码,都是基于Java 2 SDK标准版v1.2.1 javac编译器的.其他编译器生成的字节码可能略有不同. 为什么要理解字节码? 字节码就是 Java 代码的中间表示,就像是汇编代

使用ASM操作Java字节码,实现AOP原理

本文通过一个的例子来实现:使用ASM动态生成Java字节码文件(.class) 或者 加载字节码后动态修改字节码,添加我们需要执行的代码,来模拟实现Spring AOP. 年底了,也没心情抠字了,把写demo包含的几个类代码直接贴出来吧,代码拷贝下来后可以直接使用,不会有什么其他错误. 使用 asm-5.0.3.jar demo工程的package为com.shanhy.demo.asm.hello 5个Java文件: AopClassAdapter.java 用来处理哪些方法需要进行修改 Ao

Java字节码(.class文件)格式详解(三)

2.11 在ClassFile.method_info.field_info中同时存在的Attribute 2.11.1     Synthetic Attribute Synthetic Attribute用于指示当前类.接口.方法或字段由编译器生成,而不在源代码中存在(不包含类初始函数和实例初始函数).相同的功能还有一种方式就是在类.接口.方法或字段的访问权限中设置ACC_SYNTHETIC标记.   Synthetic Attribute由JDK1.1中引入,以支持内嵌类和接口(neste

关于java字节码框架ASM的学习

一.什么是ASM ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为.Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称.方法.属性以及 Java 字节码(指令).ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类. 使用ASM框架需要导入asm的jar包,下载链接:

掌握Java字节码(转)

Java是一门设计为运行于虚拟机之上的编程语言,因此它需要一次编译,处处运行(当然也是一次编写,处处测试).因此,安装到你系统上的JVM是原生的程序,而运行在它之上的代码是平台无关的.Java字节码就是你写的源代码的中间表现形式,也就是你的代码编译后的产物.你的class文件就是字节码. 简单点说,字节码就是JVM使用的代码集,它在运行时可能会被JIT编译器编译成本地代码. 你玩过汇编语言或者机器代码吗?字节码就是类似的东西,不过业界中许多人也很少会用及它,因为基本没这个必要.然而它对于理解程序

通过Java字节码发现有趣的内幕之String篇(上)(转)

原文出处: jaffa 很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西.这里没有对Java字节码知识进行介绍,如果想了解更多的Java字节码或对其感兴趣的朋友可以先阅读字节码基础:JVM字节码初探. String字面量可以通过'=='判断两个字符串是否相同,是因为大家都知道'=='是用来判断两个对象的值引用地址是否一致,两个值一样的字符串字面量定义是否指向同一个值内存地址呢?答案是肯定的. 1

什么时候我们需要修改java字节码

问题描述 spring,hibernate都有用到cglib, javassist来修改java字节码但是我不是很清楚为什么要这样做?以及他们是怎么做到的是在运行时还是编译时修改的?他们怎么确保修改后的语义是正确的呢 解决方案 javassist没有用过,说说cglib在AOP中,Java原生自带的模式需要依赖于接口,但是一些实现里面,是通过继承来实现的,压根就没有接口,或者只有很底层的接口.这种场景下,就没办法通过接口的这种动态代理方式来实现动作拦截.所以只好依赖于cglib.当然,最优雅的还