JVM的字节码指令(一步步读懂.class字节码文件的操作指令)

刚上大一那会儿就是随便一个编译器写.java后缀文件,然后通过命令行JavaC编译那个.java后缀的文件生成.class文件,然后直接java XXXX.class文件就可以运行自己的Java程序。

所以知道,Java 程序执行分两个阶段,编译阶段和运行阶段:

JavaC :这个命令就会启动Java的编译器去对Java后缀文件进行编译,生成字节码,也就是.class文件,这个文件是十六进制格式的,里面的内容有魔数,常量池,访问标志,类索引,字段表,方法表,属性表还有一堆操作指令。

Java:这个命令会启动JVM虚拟机去执行字节码,Java得益于跨平台,一次编写处处运行的优势就是基于JVM,只要按照规范写出编译器可以编译的代码(当然也可以自定义编译器进行解析)并且编译出字节码文件,JVM都能在任何平台上运行你写出来的代码。

下面看例子:

这是我写好的一个例子,不要管逻辑,重点看字节码操作指令。

JVM的字节码指令(一步步读懂.class字节码文件的操作指令)

下一步,Javac编译。

JVM的字节码指令(一步步读懂.class字节码文件的操作指令)
JVM的字节码指令(一步步读懂.class字节码文件的操作指令)

输入javap -verbose XXX.class查看查看字节码文件。

JVM的字节码指令(一步步读懂.class字节码文件的操作指令)
JVM的字节码指令(一步步读懂.class字节码文件的操作指令)
JVM的字节码指令(一步步读懂.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)则。。。。。。

这里我只是大概罗列了一下,全部的指令我也是只记住一些基本的常用的,很多我也是要靠查资料,但是能看懂就行,不去背诵也没关系,毕竟对业务开发没什么用哈哈哈哈哈。

看完这个翻译后我们再看回去字节码,是不是忽然明白它在描述做着什么了呢?

JVM的字节码指令(一步步读懂.class字节码文件的操作指令)

疫情严重,上班推迟了,趁着这几天空闲,总结出更多的博客,一是自己温习,二是给大家分享,谢谢大家,说的不对的地方请指正,鞠躬!