JVM的字节码指令(一步步读懂.class字节码文件的操作指令)
刚上大一那会儿就是随便一个编译器写.java后缀文件,然后通过命令行JavaC编译那个.java后缀的文件生成.class文件,然后直接java XXXX.class文件就可以运行自己的Java程序。
所以知道,Java 程序执行分两个阶段,编译阶段和运行阶段:
JavaC :这个命令就会启动Java的编译器去对Java后缀文件进行编译,生成字节码,也就是.class文件,这个文件是十六进制格式的,里面的内容有魔数,常量池,访问标志,类索引,字段表,方法表,属性表还有一堆操作指令。
Java:这个命令会启动JVM虚拟机去执行字节码,Java得益于跨平台,一次编写处处运行的优势就是基于JVM,只要按照规范写出编译器可以编译的代码(当然也可以自定义编译器进行解析)并且编译出字节码文件,JVM都能在任何平台上运行你写出来的代码。
下面看例子:
这是我写好的一个例子,不要管逻辑,重点看字节码操作指令。
下一步,Javac编译。
输入javap -verbose XXX.class查看查看字节码文件。
这个就是刚才写的那个小小的demo里面的全部字节码,虽然很难看,但是这已经是经过verbose工具优化后的了,如果是直接打开.class文件,会看到 cafe babe 等魔数,还有一堆十六进制的数,更加难看懂。毕竟这里的语法和格式看起来都相当于另外一门语言。
对照着下面的这个表看,应该就能一行行读懂了:
加载和存储的操作指令:
加载变量:
iload : 加载一个int类型的局部变量到栈帧的操作数栈中。
lload : 加载一个long类型的局部变量到栈帧的操作数栈中。
fload , dload : 就是加载一个 float 和 double类型的局部变量到栈帧的操作数栈中。
aload : 引用类型。(上面的都是基本类型,a 开头的load是引用类型,也就是对象或者数组的引用加载到操作数栈)
加载常量:
bipush : byte类型常量进栈
sipush : 当int类型常量取值-32768~32767时使用该指令进栈。(小一点)
Idc : 当int类型常量取值-2147483648~2147483647时使用该指令进栈。(大一点)
iconst_1 : int 类型常量值1进栈。
aconst : 引用类型常量进栈。
…当然还有很多很多,这里每列出来的就靠上网查了
存储指令(从操作数栈执行完存储到局部变量表)
istore : int 类型的。
lstore : long 类型的。
fstore dstore astore : 打字好累,反正都是一样的尿性。就是区分不同类型而已。
运算操作指令:
iadd : int类型的操作数值相加运算
isub : 相减运算
imul : 相乘运算
idiv : 相除运算
irem : int类型的操作数栈的两个值取余
ineg : int 类型取反
等等…float 和 double 的也是一个道理
类型转换:
我们经常把int转double,把char转string,就是类型转换,包括对象的上转型和下转型等。JVM中很好理解,就是等待转换的类型缩写+2+转换后的类型。
i2l : int 类型转long类型
i2c : int 转char
f2d:float转double等…
对象创建指令:
new
访问类对象中的字段:
getfield : 获取一个操作数的值。
putfield :接受一个操作数,这个操作数引用的是运行时常量池里的一个字段,在这里这个字段是simpleField。赋给这个字段的值,以及包含这个字段的对象引用,在执行这条指令的时候,都 会从操作数栈顶上pop出来。方法执行到最后的时候也会putfield一次,把操作完的操作数栈的数值pop出栈。
baload : 把数组的元素加载到操作数栈,aload我们知道是引用类型入栈,前面再加一个基本数据类型的缩写就是具体引用类型的某个元素。caload (char) iaload ( int ) laload ( long ) daload ( double ) aaload ( 引用类型中的引用类型,比如Java实现二叉树中的对象包对象 )
astore : 把操作数栈值存储到数组元素位置。
调用方法指令:
invokeinterface : 接口方法,运行时搜索实现了该接口方法的对象,并找到对应方法执行。
invokespecial : 特殊处理的实例化方法,初始化方法,私有方法,父类方法。
invokestatic : 调用了类方法,也就是static方法。main方法就是一个invokestatic调用。
返回指令:
return : 也代表着方法的执行结束,同时这个方法的栈帧从栈(方法运行描述模型)中弹出。
异常指令:
athrow
我们代码中编写的try catch其实是通过控制转移指令和athrow异常指令实现的。在字节码中是没有try catch语句块的字节码的,不信自己写个try catch看一下就知道了。
控制转移指令:
goto #XX行
有的人蒙蔽了,不是说Java没有goto吗?这里的goto是字节码文件里面的,Java里面的goto保留字不同,这里是打破代码的顺序执行机制,强制它跳过几行或者重新执行几行,这就是控制转移指令。
ifeq : 假如相等,则 执行到# XX行。
iflt : 假如小于(if less than )则。。。
ifgt : 假如大于,(greater than)则。。。。。。